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

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

Related

How to save and then later delete timeoutId in React inside a async thunk that is dispatched

I have a problem that I don't know how to fix.
I want to call refresh-token endpoint when setTimeout time runs out. setTimeout is called in 2 different ways:
When user logs in
When user enters a page again and has bearer token
If I just called setTimeout without deleting the previous one, a user could log in and out and log in again and it would send a request to the refresh-token endpoint for every setTimeout that was created (in this case, 2 requests).
What I want to do is save the setTimeout that was created and if user decides to log out and then log in again, we delete setTimeout and create a new one. However, when I store my timeoutId with useState, I get Invalid hook call error.
This is how my code looks like:
storageActions.ts
export function setRefreshTokenTimeout(bearerToken: string, refreshToken: string) {
removeRefreshTokenTimeout();
var decodedBearerToken: any = jwt_decode(bearerToken);
let remainingDuration = calculateRemainingTime(decodedBearerToken.exp * 1000);
remainingDuration = 10000;
const refreshTokens = setTimeout(() => store.dispatch(refreshTokensThunk({ refreshToken })), remainingDuration);
setRefreshTokensTimeout(refreshTokens);
}
export function removeRefreshTokenTimeout() {
if (refreshTokensTimeout) {
clearTimeout(refreshTokensTimeout);
setRefreshTokensTimeout(undefined);
}
}
authActions.ts
export const userLoginThunk = createAsyncThunk<UserLoginResponse, UserLoginRequest>(
"auth/user-login",
async (request, thunkAPI) => {
let response = await AuthServiceUserLogin(request);
setUserLocalStorageData(response.data);
setRefreshTokenTimeout(response.data.bearerToken, response.data.refreshToken);
return response.data;
}
);
export const authUserThunk = createAsyncThunk("auth/authUser", async thunkAPI => {
await AuthServiceAuthUser();
const userStorage = getUserLocalStorageData();
if (userStorage?.data?.bearerToken) {
setRefreshTokenTimeout(userStorage.data.bearerToken, userStorage.data.refreshToken);
}
});
All of these are called inside a createAsyncThunk that is dispatched. Is there any way to solve this problem?
I knew this was because of useState hook, but I thought that you can only save states with hooks like useState or useRef.
I just figured out that this rule only refers to React components. storageActions.ts isn't a component, therefore I can use a simple variable to save my timeoutId.
I tested it and it works.

React & RTK: How to dispatch from a middleware?

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.

React: Stop hook from being called every re-rendering?

Somewhat new to React and hooks in React. I have a component that calls a communications hook inside of which a call to an API is made with AXIOS and then the JSON response is fed back to the component. The issue I'm having is the component is calling the hook like six times in a row, four of which of course come back with undefined data and then another two times which returns the expected JSON (the same both of those two times).
I did a quick console.log to double check if it was indeed the component calling the hook mulitple times or it was happening inside the hook, and it is the component.
How do I go about only have the hook called only once on demand and not multiple times like it is? Here's the part in question (not including the rest of the code in the widget because it doesn't pertain):
export default function TestWidget() {
//Fetch data from communicator
console.log("called");
const getJSONData = useCommunicatorAPI('https://jsonplaceholder.typicode.com/todos/1');
//Breakdown passed data
const {lastName, alertList, warningList} = getJSONData;
return (
<h1 id="welcomeTitle">Welcome {lastName}!</h1>
);
}
export const useCommunicatorAPI = (requestAPI, requestData) => {
const [{ data, loading, error }, refetch] = useAxios('https://jsonplaceholder.typicode.com/todos/1', []);
console.log("data in Communicator:", data);
return {data};
}
I would use the useEffect hook to do this on mount and whenever any dependencies of the request change (like if the url changed).
Here is what you will want to look at for useEffect
Here is what it might look like:
const [jsonData, setJsonData] = React.useState({})
const url = ...whatver the url is
React.useEffect(() => {
const doFetch = async () => {
const jsonData = await useAxios(url, []);;
setJsonData(jsonData)
}
doFetch();
}, [url])
...use jsonData from the useState
With the above example, the fetch will happen on mount and if the url changes.
Why not just use the hook directly?
export default function TestWidget() {
const [{ data, loading, error }, refetch] =
useAxios('https://jsonplaceholder.typicode.com/todos/1', []);
return (<h1 id="welcomeTitle">Welcome {lastName}!</h1>);
}
the empty array [] makes the hook fire once when called
Try creating a function with async/await where you fetch the data.
Here can you learn about it:
https://javascript.info/async-await

How to prevent UI freeze when calling API with axios

I am trying to load data when my component loads using componentDidMount. However calling the Redux action, making the call with axios seems to freeze the UI. When I have a form with 12 inputs and one makes an API call I would assume I can type in the other inputs and not have them freeze up on me.
I've tried reading some other posts on the subject but they are all a little different and everything I have tried doesn't seem to resolve the issue.
I am working on linux using React 16.8 (when using RN I use 55.4)
I have tried making my componentDidMount async as well as the redux-thunk action. It didn't seem to help anything, so I must be doing something wrong.
I tried doing the following with no success. Just using short form for what I tried. Actual code listed below.
async componentDidMount() {
await getTasks().then();
}
And I tried this
export const getTasks = () => (async (dispatch, getState) => {
return await axios.get(`${URL}`, AJAX_CONFIG).then();
}
Current Code:
Component.js
componentDidMount() {
const { userIntegrationSettings, getTasks } = this.props;
// Sync our list of external API tasks
if (!isEmpty(userIntegrationSettings)) {
getTasks(userIntegrationSettings.token)
// After we fetch our data from the API create a mapping we can use
.then((tasks) => {
Object.entries(tasks).forEach(([key, value]) => {
Object.assign(taskIdMapping, { [value.taskIdHuman]: key });
});
});
}
}
Action.js
export const getTasks = () => ((dispatch, getState) => {
const state = getState();
const { token } = state.integrations;
const URL = `${BASE_URL}/issues?fields=id,idReadable,summary,description`;
const AJAX_CONFIG = getAjaxHeaders(token);
dispatch(setIsFetchingTasks(true));
return axios.get(`${URL}`, AJAX_CONFIG)
.then((response) => {
if (!isEmpty(response.data)) {
response.data.forEach((task) => {
dispatch(addTask(task));
});
return response.data;
} else {
dispatch(setIsFetchingTasks(false));
}
})
.catch((error) => {
dispatch(setIsFetchingTasks(false));
errorConsoleDump(error);
errorHandler(error);
});
});
reducer.js
export default (state = defaultState, action) => {
switch (action.type) {
case ADD_TASK:
case UPDATE_TASK:
return update(state, {
byTaskId: { $merge: action.task },
isFetching: { $set: false }
});
default:
return state;
}
};
So in my answer what are you going to learn?
General data loading with Redux
Setting up a component lifecycle method such as componentDidMount()
Calling an action creator from componentDidMount()
Action creators run code to make an API request
API responding with data
Action creator returns an action with the fetched data on the payload property
Okay, so we know there are two ways to initialize state in a Reactjs application, we can either invoke a constructor(props) function or we can invoke component lifecycle methods. In this case, we are doing component lifecycle methods in what we can assume is a class-based function.
So instead of this:
async componentDidMount() {
await getTasks().then();
}
try this:
componentDidMount() {
this.props.fetchTasks();
}
So the action creators (fetchTasks()) state value becomes the components this.props.fetchTasks(). So we do call action creators from componentDidMount(), but not typically the way you were doing it.
The asynchronous operation is taking place inside of your action creator, not your componentDidMount() lifecycle method. The purpose of your componentDidMount() lifecycle method is to kick that action creator into action upon booting up the application.
So typically, components are generally responsible for fetching data via calling the action creator, but it's the action creator that makes the API request, so there is where you are having an asynchronous JavaScript operation taking place and it's there where you are going to be implementing ES7 async/await syntax.
So in other words it's not the component lifecycle method initiating the data fetching process, that is up to the action creator. The component lifecycle method is just calling the action creator that is initiating the data fetching process a.k.a. the asynchronous request.
To be clear, you are able to call this.props.fetchTasks() from your componentDidMount() lifecycle method after you have imported the action creator to your component like and you have imported the connect function like so:
import React from "react";
import { connect } from "react-redux";
import { fetchTasks } from "../actions";
You never provided the name of the component you are doing all this in, but at the bottom of that file you would need to do export default connect(null, { fetchTasks })(ComponentName)
I left the first argument as null because you have to pass mapStateToProps, but since I don't know if you have any, you can just pass null for now.
Instead of this:
export const getTasks = () => (async (dispatch, getState) => {
return await axios.get(`${URL}`, AJAX_CONFIG).then();
}
try this:
export const fetchTasks = () => async dispatch => {
const response = await axios.get(`${URL}`, AJAX_CONFIG);
dispatch({ type: "FETCH_TASKS", payload: response.data });
};
There is no need to define getState in your action creator if you are not going to be making use of it. You were also missing the dispatch() method which you need when developing asynchronous action creators. The dispatch() method is going to dispatch that action and send it off to all the different reducers inside your app.
This is also where middleware such as Redux-Thunk comes into play since action creators are unable to process asynchronous requests out of the box.
You did not show how you wired up your redux-thunk, but it typically goes in your your root index.js file and it looks like this:
import React from "react";
import ReactDOM from "react-dom";
import "./index.scss";
import { Provider } from "react-redux";
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import App from "./components/App";
import reducers from "./reducers";
const store = createStore(reducers, applyMiddleware(thunk));
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.querySelector("#root")
Remember that connect function I said you needed to implement? That came into being as a result of implementing or you should have implemented the Provider tag. With the Provider tag, your components can all have access to the Redux store, but in order to hook up the data to your components you will need to import the connect function.
The connect function is what reaches back up to the Provider and tells it that it wants to get access to that data inside whatever component you have that lifecycle method in.
Redux-Thunk is most definitely what you needed to implement if you have corrected everything as I have suggested above.
Why is Redux-Thunk necessary?
It does not have anything intrinsically built into it, it's just an all-purpose middleware. One thing that it does is allow us to handle action creators which is what you need it to be doing for you.
Typically an action creator returns an action object, but with redux-thunk, the action creator can return an action object or a function.
If you return an action object it must still have a type property as you saw in my code example above and it can optionally have a payload property as well.
Redux-Thunk allows you to return either an action or function within your action creator.
But why is this important? Who cares if it returns an action object or a function? What does it matter?
That's getting back to the topic of Asynchronous JavaScript and how middlewares in Redux solves the fact that Redux is unable to process asynchronous JavaScript out of the box.
So a synchronous action creator instantly returns an action with data ready to go. However, when we are working with asynchronous action creators such as in this case, it takes some amount of time for it to get its data ready to go.
So any action creator that makes an network request qualifies as an asynchronous action creator.
Network requests with JavaScript are asynchronous in nature.
So Redux-Thunk, being a middleware which is a JavaScript function that is going to be called with every single action that you dispatch. The middleware can stop the action from proceeding to your reducers, modify the action and so on.
You setup dispatch(setIsFetchingTasks(true)) but when axios returns you never set it to false. Did you miss to add dispatch(setIsFetchingTasks(false)) before return response.data;?
This could be the reason if your UI waits for the fetchingTasks to finish

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