Is it not correct to do api call without useEffect? - javascript

I want to do an api call after a button is clicked in react.But I have read that to do async tasks, we use useEffect.
So is it okay to not use useEffect and do an api call without it?
I think that without using useEffect an api call would block the render.
useEffect runs depending on deps Array.
It is used to do async tasks.
But I want to do a api call onClick.So its not possible to use useEffect.
So,What is the correct way to do an api call if it has to be done on Click?

You can do api call with and w/o the useEffect, both are fine.
And no, you won't block the api call if you don't use useEffect.
const App = () => {
const makeApiCall = async () => {
// the execution of this function stops
// at this await call, but rest of the App component
// is still executes
const res = await fetch("../");
}
...
};

Related

Debounce with useCallback in React

I have a function that I'd like to debounce in my React project, using Lodash's debounce library.
The high level structure is like so (greatly simplified for purposes of this question):
I have a function that can be called multiple times, but should only trigger callApiToSavetoDatabase() once every 3 seconds.
const autoSaveThing = useRef(debounce(() => {
callApiToSaveToDatabase();
}, 3000)).current;
This API calls an asynchronous function that sets react State and calls an API.
const callApiToSaveToDatabase = useCallback(async () => {
console.log('Started API function');
setSomeState(true);
try {
const response = await apiCall(data);
} catch {
// failure handling
}
}, [ /* some dependencies */ ]);
What works:
callApiToSavetoDatabase() is correctly only called once during the debounce period.
What doesn't work:
We hit the console.log line in callApiToSavetoDatabase() but from debugging in the browser, the code quits out of callApiToSavetoDatabase() as soon as I set state with setSomeState(true).
Is there some limitation with setting state inside a useCallback function I'm hitting here?
It's worth noting that if I call callApiToSavetoDatabase() directly it works perfectly.
The issue here ended up being that my callApiToSaveToDatabase() function was both:
Inside my React component and
Also setting React state
This combination cause a component re-render which subsequently halted my function.
The solution here was to move callApiToSaveToDatabase() outside of the React component, and pass to it all the necessary state variables and setState function references it needed. An example of this is as follows:
// function that calls my API
const callApiToSaveToDatabase = async (someState,setSomeSTate) => {
setSomeState(true);
try {
const response = await apiCall(someState);
} catch {
// failure handling
}
};
// debounce wrapper around the API function above
const autoSaveThing = debounce((someState,setSomeState) => callApiToSaveToDatabase(someState,setSomeState), 3000));
// my React component
const myComponent = () => {
// some code
autoSaveThing(someState,setSomeState);
}

Async/Await not executing as expected

I have the below method where I am updating store and after store updating, I am performing certain activities based on store values -
useEffect(()=>{
const asyncUpdateStore=async()=>{
await updateStore();
getDetails(storeValues) //this is api call made based on updated store values above
.then(()=>{...})
}
asyncUpdateStore();
},[applyChange]);
Upon execution of this code , I find that getDetails which is internally a axios call is not waiting for store values to be get updated within updateStore() method.
When useEffect is getting called second time , I find store is updated.
I want to wait execution of getDetails , till updateStore method finishes its execution.
I have also tried with -
useEffect(()=>{
const asyncUpdateStore=async()=>{
await updateStore();
}
asyncUpdateStore();
getDetails(storeValues) //this is api call made based on updated store values above
.then(()=>{...})
},[applyChange]);
Edit 1:
updateStore() method involves a dispatch call.
const updateStore=()=>{
const data:IData={
value1:valuestate1
value2:valuestate2
}
dispatch(updateData(data));
}
In redux, all dispatches are synchronous. Using await has no effect there. If updateData() is an asynchronous action, then you may need look at the documentation of the middleware you are using, to handle async actions (i.e redux-thunk, etc).
Usually, the middleware will provide 3 states ("pending", "success", "failed") that you can store in your redux store, and then read in your component. The logic flow could look like this.
//example route that would store the current status of an async response
const asyncState = useSelector(state => state.storeValues.status)
const storeValues = useSelector(state => state.storeValues.data)
useEffect(()=>{
//runs everytime applyChange changes
dispatch(updateData(data));
},[applyChange, dispatch, updateData]);
//runs everytime an async status changes, due to the api request done above
useEffect(()=>{
//success indicates the promise resolved.
if(asyncState === "success")
getDetails(storeValues) //this is api call made based on updated store values above.then(()=>{...})
},[asyncState]);
To understand how async patterns work in redux, or see more examples, you can check out redux's own tutorial and docs here. The have a conceptual diagram of state flow, as well as a ton of guides.
Note: dispatch actions should never be anything but synchronous, as reducers should not have side effects. Redux will throw errors and complain if an action is async, and you may see unexpected behavior in your store if async actions aren't handled outside reducers first.

How to stop executing a command the contains useState?

This is my code which sends a GET request to my backend (mySQL) and gets the data. I am using useState to extract and set the response.data .
const baseURL = 'http://localhost:5000/api/user/timesheet/13009';
const [DataArray , setDataArray] = useState([]);
axios.get(baseURL).then( (response)=>{
setDataArray(response.data);
});
But useState keeps on sending the GET request to my server and I only want to resend the GET request and re-render when I click a button or execute another function.
Server Terminal Console
Is there a better way to store response.data and if not how can I stop automatic re-rendering of useState and make it so that it re-renders only when I want to.
As pointed out in the comments, your setState call is triggering a re-render which in turn is making another axios call, effectively creating an endless loop.
There are several ways to solve this. You could, for example, use one of the many libraries built for query management with react hooks, such as react-query. But the most straightforward approach would be to employ useEffect to wrap your querying.
BTW, you should also take constants such as the baseUrl out of the component, that way you won’t need to include them as dependencies to the effect.
const baseURL = 'http://localhost:5000/api/user/timesheet/13009';
const Component = () => {
const [dataArray , setDataArray] = useState([]);
useEffect(() => {
axios.get(baseURL).then( (response)=>{
setDataArray(response.data);
});
}, []);
// your return code
}
This would only run the query on first load.
you have to wrap your request into a useEffect.
const baseURL = 'http://localhost:5000/api/user/timesheet/13009';
const [DataArray , setDataArray] = useState([]);
React.useEffect(() => {
axios.get(baseURL).then((response)=>{
setDataArray(response.data);
})
}, [])
The empty dependency array say that your request will only be triggered one time (when the component mount). Here's the documentation about the useEffect
Add the code to a function, and then call that function from the button's onClick listener, or the other function. You don't need useEffect because don't want to get data when the component first renders, just when you want to.
function getData() {
axios.get(baseURL).then(response => {
setDataArray(response.data);
});
}
return <button onClick={getData}>Get data</button>
// Or
function myFunc() {
getData();
}

Access Token in React.useEffect generally works, but comes back undefined when page is refreshed / reloaded

I have a simple React page / component in Gatsby that makes an API call. For this API call I need a token. I use gatsby-theme-auth0 to obtain this token via their AuthService object.
I am starting the API call in my useEffect. It looks like this:
useEffect(() => {
//defining the async function
async function fetchFromAPI() {
try {
const data = await fetchData()
setData(data)
}
}
//executing the async function:
fetchFromAPI()
}, [])
The function fetchData(), which is asynchronously called in useEffect currently looks like so:
async function fetchData() {
const client = new GraphQLClient(SERVER_URL_GRAPHQL)
let aToken = await AuthService.getAccessToken()
client.setHeader('authorization', `Bearer ${aToken}`)
const query = ...
const data = await client.request(query)
return data
}
All of this generally works. When I navigate to this page, from a different page within my SPA it works. However, when I reload the page, it doesn't. the access token (aToken) then comes back as undefined.
But: I can make things work, when I wrap a setTimeout around the whole call. Then the access token comes back fine and isn't undefined. So I guess something first needs to initialise before AuthService can be called? I'm just not sure how to ensure this.
But this is not what I want to do in production. Now I am wondering why this is. Maybe I am using useEffect the wrong way? Unfortunately, I have not been able to find anything online or on github so far. I'm sure the problem is rather basic though.
EDIT: The AuthService.getAccessToken() method can be found here It's part of gatsby-theme-auth0
EDIT: To clarify, the server does receive the request and sends back {"error":"jwt malformed"} - which makes sense, since it's undefined.
I don't know if you have the authentication in a hook already or not, but you need to check if the user is authenticated before you make any api call, especially those that on app init. Do you have a hook/context when you handle the authentication ? If you have, you can change your code a bit
const {isAuthenticated} = useContext(userAuthenticatedContext)
useEffect(() => {
//defining the async function
async function fetchFromAPI() {
try {
const data = await fetchData()
setData(data)
}
}
//executing the async function:
if(isAuthenticated) fetchFromAPI()
}, [isAuthenticated])
This way, isAuthenticated is a dependency in your useEffect and it will run again when the value of isAuthenticated is changed and it will not fail as you are doing a check, before making the call.
getAccessToken relies on that modules' this.accessToken value to be set. It looks like you need to call either handleAuthentication or checkSession prior to making your call so that the value gets initialized properly. Consider putting checkSession somewhere that runs when the page loads.

Is it synchronous to dispatch three actions inside a onClick functions and then map the data using that reponses

I want to parse one excel sheet and before parsing I want some data from backend to map it.
So after clicking on Submit button, I want to trigger three actions one by one and store the response inside store. I am using redux-saga for this.
After the three action (api calls), I will call the parsing function and do the parsing and mapping using that response I will be fetching from store.
I have tried dispatching the three actions one by one. But as soon as it reaches the network client i.e axios instance to call api it becomes async and the next line gets executed.
onSubmit = () => {
/* I will set the loader on submit button till the api is called and all parsing of excel sheet is done. */
this.setState({
showLoader: true,
}, () => {
this.props.getData1(); //Will be saving it in store as data1
this.props.getData2(); //Will be saving it in store as data2
this.props.getData3(); //Will be saving it in store as data3
/* After this I want to call the parsing function to parse the excel sheet data and map accordingly */
parseExcelData(sheetData); //sheet data is the excel data
}
So I expected that when I will call the 'parseExcelData' function, the data from store i.e data1, data2,and data3 will be available in that function.
But all the api call happens after the sheet is being parsed.
I have done it using saga generator functions and is working fine. But I want to know how to deal with this situation with redux.
Putting an api call (or any other async operation) into a saga does not make that action synchronous, it is still async. Separately, redux-saga really does not support getting a result from an action -- you trigger a saga with an action, so when the saga completes, it has no way to return a result to the code that originally triggered it. (You can try to work around this by passing a callback along with the action that triggers the saga, and have the saga call the callback, but I wouldn't recommend this approach.)
I would recommend implementing this without redux-saga, using traditional action creators. The action creators would return promises that make the async api calls, and resolve with the result when they're finished. That might look something like this:
// action creator getData1, getData2, getData3
export const getData1 = () => {
return fetch(apiUrl).then(result => {
return result.json();
}).then(resultJson => {
// also fire an action to put it in the store here
// if other parts of your app need the data
return resultJson;
}).catch(err => {
console.error(err);
});
};
// react component
// assumes 1, 2, and 3 cannot be parallelized
// could also be written with .then instead of await
onSubmit = async () => {
this.setState({showLoader: true}, () => {
const result1 = await this.props.getData1();
const result2 = await this.props.getData2(result1);
const result3 = await this.props.getData3(result2);
});
}
You could have the action creators dispatch an action to put the data in the store instead of resolving the promise with the result. But that means you have to pick up the new data via the component's props, which probably means something in componentDidUpdate that checks if the new props are different from the old props, and if so, calls the next data-fetcher. IMO that approach is much more awkward.

Categories