Reactjs using useState causing loop of axios requests - javascript

I am developing a reactjs app. I am trying to list users from the data of an ajax response. Below is the code.
const [ users, setUsers ] = useState([]);
useEffect(() => {
const config = {
headers: {
Authorization: 'Bearer ' + localStorage.getItem('token')
}
}
axios.get('users', config).then((res) => {
if (res.status == 200 && res.data.msg == "success") {
setUsers(res.data.users)
}
else {
history.push("/");
}
feather.replace()
});
});
And I am using the code below to list the users in a table.
function getRows(users) {
return users.map(user => {
const { u_id, username } = user;
return (
<tr key={u_id} id={u_id}>
<td>{u_id}</td>
<td>{username}</td>
<td class="delete">
<span data-feather="edit"></span></span>
</td>
</tr>
)
});
}
Now the problem is that it is generating an endless loop of requests to fetch the users. I think it is because each time the state changes react is calling the useEffect hook. How to fix this.

useEffect(() => {
const config = {
headers: {
Authorization: 'Bearer ' + localStorage.getItem('token')
}
}
axios.get('users', config).then((res) => {
if (res.status == 200 && res.data.msg == "success") {
setUsers(res.data.users)
}
else {
history.push("/");
}
feather.replace()
});
}, []);
note here I passed the empty array[] as the second argument for useEffect,
Reason of empty array at useEffect
If you want to run an effect and clean it up only once (on the mount and unmount), you can pass an empty array ([]) as a second argument. This tells React that your effect doesn’t depend on any values from props or state, so it never needs to be re-run. This isn’t handled as a special case — it follows directly from how the dependencies array always works.
read more about useEffect

You would want to add an empty array as useEffect dependency, so it only run once:
useEffect(() => {
const config = {
headers: {
Authorization: "Bearer " + localStorage.getItem("token"),
},
};
axios.get("users", config).then((res) => {
if (res.status == 200 && res.data.msg == "success") {
setUsers(res.data.users);
} else {
history.push("/");
}
feather.replace();
});
}, []); //<-- Add empty dependency here

The useEffect hook basically accepts two arguments, the first one being the callback, and the second one is an array of dependencies (optional). The purpose of the second argument (array of dependencies) is to trigger the callback function, whenever any value in the dependencies array change. So, if you don't use the dependencies array in your useEffect hook, the callback function is executed every time the component is rendered, i.e, whenever the state variables are mutated.
So what's causing the loop?
const [ users, setUsers ] = useState([]);
useEffect(() => {
const config = {
headers: {
Authorization: 'Bearer ' + localStorage.getItem('token')
}
}
axios.get('users', config).then((res) => {
if (res.status == 200 && res.data.msg == "success") {
setUsers(res.data.users) //Updating state causes the component to re-render
}
else {
history.push("/");
}
feather.replace()
});
});
Looking at your code, we can observe that you are using two hooks ( useState and useEffect) , so when the promise is resolved, you are updating your users state with the latest fetched information. So far, so good. When a state is updated, we know that the component is re-rendered (that's the basic principle on how React works). So updating the users state causes the component function to re-render, which would again invoke useEffect which would again update users state, and so forth.
Workaround
To avoid this loop, we'd want to execute useEffect only once, and not every time the component is rendered. This could be accomplished by passing an empty array as the second argument (Which basically tells React to run `useEffect` only when the component is rendered for the first time).
const [ users, setUsers ] = useState([]);
useEffect(() => {
const config = {
headers: {
Authorization: 'Bearer ' + localStorage.getItem('token')
}
}
axios.get('users', config).then((res) => {
if (res.status == 200 && res.data.msg == "success") {
setUsers(res.data.users) //Updating state causes the component to re-render
}
else {
history.push("/");
}
feather.replace()
});
},[]); //Empty array as the second argument
This is similar to componentDidMount when working with class-based components.
Further, you could also add variables to the array that would cause the useEffect to be invoked only when the variables in the array are mutated.

Add dependency array to you're useEffect hook , this will solve you're problem
this will act like component did mount
useEffect(()=> {
/// you're code
} , [])
but the one that you used acts like component did update and causes problem because you need to execute the code only when component mounted

Related

react component constantly fetching and updating data for no reason

I have a react component that I'm fetching data with to visualize.
The data fetching is happening constantly, instead of just once as is needed. I was wondering if there is a way to reduce this happening.
The component is like this,
export default function Analytics() {
const {
sentimentData,
expressionsData,
overall,
handleChange,
startDate,
endDate,
sentimentStatistical,
} = useAnalytics();
return (
I'm wondering if I should be using something like componentDidMount() here with componentDidUpdate() ?
UseAnalytics is another component specifically for fetching data, basically just a series of fetches.
There are different buttons to click on the site that can change the data requested, so I do want to be able to change the state of these data objects / request more, i.e., I filter the data based on dates. But confused how to stop it just constantly requesting data.
Thanks in advance,
Update to share the function being called.
export default function useAnalytics() {
let d = new Date();
d.setMonth(d.getMonth() - 1);
const [dateRange, setDateRange] = useState([d.getTime(), Date.now()]);
const [startDate, endDate] = dateRange;
const { data: sentimentData } = useSWR(
`dashboard/sentiment/get-sentiment-timefilter?startTime=${startDate}&endTime=${endDate}`,
fetchSentiment
);
const { data: expressionsData } = useSWR(
`dashboard/expression/get-expression-analytics?startTime=${startDate}&endTime=${endDate}`,
apiRequest
);
return {
sentimentData,
expressionsData,
overall,
handleChange,
setDateRange,
sentimentStatistical,
startDate,
endDate,
};
}
The apirequest is like this,
export async function apiRequest(path, method = "GET", data) {
const accessToken = firebase.auth().currentUser
? await firebase.auth().currentUser.getIdToken()
: undefined;
//this is a workaround due to the backend responses not being built for this util.
if (path == "dashboard/get-settings") {
return fetch(`/api/${path}`, {
method,
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${accessToken}`,
},
body: data ? JSON.stringify(data) : undefined,
})
.then((response) => response.json())
.then((response) => {
if (response.error === "error") {
throw new CustomError(response.code, response.messages);
} else {
return response;
}
});
}
return fetch(`/api/${path}`, {
method,
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${accessToken}`,
},
body: data ? JSON.stringify(data) : undefined,
})
.then((response) => response.json())
.then((response) => {
if (response.status === "error") {
// Automatically signout user if accessToken is no longer valid
if (response.code === "auth/invalid-user-token") {
firebase.auth().signOut();
}
throw new CustomError(response.code, response.message);
} else {
return response.data;
}
});
}
With the answers,
useEffect(()=>{
// this callback function gets called when there is some change in the
// state variable (present in the dependency array)
},[state variable])
This seems about right, I'm wondering how to substantiate the constants outside of useAnalytics?
In the functional component we don't have componentDidMount() or componentDidUpdate () but we have the workaround for the same by using the useEffect react hook.
For implementing the functionality of componentDidMount() we can utilize the following code snippet
useEffect(()=>{
// this is the callback function that needs to be called only once (when the component has mounted)
},[])
And for implementing the functionality of componentDidUpdate() we can utilize the following code snippet
useEffect(()=>{
// this callback function gets called when there is some change in the
// state variable (present in the dependency array)
},[state variable])
First of all, You are using function component, here you cannot use ComponentDidMount() or ComponentDidUpdate() as they only work in class Components. You will have to use useEffect() but you haven't provided any additional code to understand the situation here.
Anyway if you are setting some state on button click when you fetch data and it is occurring again and again it's probably because you haven't used second argument on useEffect() which states that this useEffect() will only run when the second argument changes.
So to answer your question, pass second argument to useEffect() that you are setting when you click a button.
useEffect(() => {
//
}, [state])
As per your code it's functional component and you can't use componentDidMount() and componentDidUpdate() method as this functions are used in class component.
If you want prevent constantly updating component then there are different ways for both class and functional component.
For Class Component: Only executes if old and new data doesn't match. this function does comparison between old and new value.
componentDidUpdate(prevProps, prevState) { if (prevState.data !== this.state.data) { // Now fetch the new data here. } }
For Functional Component: This is only execute when previous state change.
useEffect(() => { // your data }, [stateName])

Is there a way to put react hooks in a function that's not function component?

I'm trying to create a function that is not a component nor hooks that's callable on speficic event. Let's say i have a simple function that post a data using axios and i want to use navigate after the post is successfull. Here's the example
export const authLogin = (email, password) => {
const config = {
withCredentials: true,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'X-CSRFToken': Cookies.get('csrftoken')
}
}
let navigate = useNavigate();
return dispatch => {
console.log('Masuk ke dalam auth file');
dispatch(authStart());
axios.post('/log_in/', {
email: email,
password: password
}, config)
.then(res => {
if (res.data.error) {
alert(res.data.error)
dispatch(authFail(res.data.error))
}
else {
const token = res.data.key;
const expirationDate = new Date(new Date().getTime() + 3600 * 1000);
localStorage.setItem('token', token);
localStorage.setItem('expirationDate', expirationDate);
dispatch(authSuccess(token));
dispatch(checkAuthTimeout(3600));
alert('login berhasil')
navigate("/", { replace: true });
}
})
.catch(err => {
alert(err);
dispatch(authFail(err))
})
}
}
I have an error that says
but when i try to change the function name with an uppercase letter, another problem occured, how do i resolve this problem?
A hook must be attached to a fiber which is directly attached to the React component tree. You CANNOT use hooks outside the component tree because React can't keep track of them (this is why hooks must always be run in the same order, and can't be conditional, because React keeps track of their state internally).
The only time you can use a hook outside of a component, is from another hook.
In short, you must be able to draw a straight line back from the hook call to React rendering the component tree. If you cannot, then it's an invalid hook call.
THE SOLUTION
...in your case - is to simply pass in the navigate function to your action as a parameter:
const MyComponent = () => {
const navigate = useNavigate();
const doSomethingHandler = () => {
dispatch(authLogin(email,password,navigate))
}
}
const authLogin = (email,password,navigate) => {
// ...do your action and call the `navigate` parameter
// when you need to
}

how do I convert classic reactjs to hooks?

I am trying to covert the following to hooks, but however I am getting some issues to translate this line this.fetchPlaces = debounce(this.fetchPlaces, 200); what is the exact match for hooks?
state = {
q: '',
places: [],
}
fetchPlaces(q) {
get(`http://www.example.com/places/`, {
params: {q}
}).then(response => {
this.setState({places: response.data.places});
});
}
constructor(props) {
super(props);
this.fetchPlaces = debounce(this.fetchPlaces, 200);
}```
I'd recommend you to go through React Hooks' official guide, by the end of the guide lies a full example of how to use react hooks.
You should use useState hook to manage states and use useEffect hook to load data from network.
const Foo = () =>{
// all hooks should be used in top level of the function component.
// orders matter.
// https://www.reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level
const [q, setQ] = useState(''); // use hooks to declare q
const [places, setPlaces] = useState([]); // use hooks to declare places
// load data from network after React Nodes are rendered
useEffect(()=>{
get(`http://www.example.com/places/`, {
params: {q}
}).then(response => {
setPlaces(response.data.places); // call setPlaces to set state
});
}
,[1]); // passing a constant to the second Array parameter to strict the hook be called only for once, if left undefined, useEffect will be called after every render.
return // React Nodes
}
I've used react hooks for a while, you can checkout my full example here.
https://github.com/ShinChven/bootcamp/blob/master/console/src/pages/Admin/Users/Users.tsx
I do not see why you should call network with debounce, but here's a post for that.
https://www.freecodecamp.org/news/debounce-and-throttle-in-react-with-hooks/
const fetchPlaces = (q) => {
get(`http://www.example.com/places/`, {
params: {q}
}).then(response => {
setPlaces(response.data.places);
});
}
const deboucedFetchPlaces = debounce(fetchPlaces, 200)
Then in your code you can call deboucedFetchPlaces
You can do it like this.
const fetchMore = _.debounce(fetchPlaces, 500);
fetchPlaces(q) {
get(`http://www.example.com/places/`, {
params: {q}
}).then(response => {
this.setState({places: response.data.places});
});
}
fetchMore();

Why doe the hook fire itself in an infinite loop when I am passing the update props?

I have created a custom hook that makes an HTTP request. But the problem is that it makes requests in an infinite loop. I cannot understand the reason for this. I want the request to fire, if there is either a change in fetchConfig or action
export default function useBrokerFetch<T>(fetchConfig: { [key: string]: any }, action: BrokerActions): [T, boolean] {
const [data, setData] = useState(null as T);
const [loading, setLoading] = useState(true);
const brokerEndPoint = process.env.REACT_APP_APIBROKER_ENDPOINT;
async function fetchUrl() {
try {
const {data} = await BrokerHttpRequest(new Request(brokerEndPoint, {
headers: {
"Accept": "application/json",
"Content-Type": "application/json"
},
method: "POST",
body: JSON.stringify({
"data": fetchConfig,
"signature": BrokerServiceProviders.WMS,
"action": action
})
}));
if (data) {
setData(data);
setLoading(false);
} else {
/**
* The request with the broker failed or the broker
* did not return any data. In either case, set the
* broker data to undefined and update the loading
* state to "not-loading"
*/
setData(undefined as T);
setLoading(false);
}
} catch (err) {
setData(undefined as T);
setLoading(false);
}
}
useEffect(() => {
fetchUrl();
}, [fetchConfig, action]);
return [data, loading];
}
What is it that I am doing wrong?
So I am not an expert on react but here are my 2 cents. I think every time you update "data" in your http-request your parent component depends on it an re-renders. When the parent re-renders their objects/child components are initialized again and useBrokerFetch is called again.
In this case "data" or "actions" might still be of the same value but for useEffect it matters if the "are equal" to the props provided. Since the parent re-rendered they are not the same. Please keep in mind, that also functions are up to change in JS.
I recommend the useCallback-Hook to you. With useCallback() you can basically cache a value you then provide as props to useBrokerFetch(). When your parent re-renders the props action and data stay equal and useEffect should not be called again. You use useCallback similar to useEffect, check out the docs.

Why is my custom hook called so many times?

I'm trying to implement a custom hook to provide the app with a guest shopping cart. My hook wraps around the useMutation hook from Apollo and it saves the shopping cart id in a cookie while also providing a function to "reset" the cart (basically, to remove the cookie when the order is placed).
Code time! (some code omitted for brevity):
export const useGuestCart = () => {
let cartId;
const [createCart, { data, error, loading }] = useMutation(MUTATION_CREATE_CART);
console.log(`Hook!`);
if (!cartId || cartId.length === 0) {
createCart();
}
if (loading) {
console.log(`Still loading`);
}
if (data) {
console.log(`Got cart id ${data.createEmptyCart}`);
cartId = data.createEmptyCart;
}
const resetGuestCart = useCallback(() => {
// function body here
});
return [cartId, resetGuestCart];
};
In my component I just get the id of the cart using let [cartId, resetCart] = useGuestCart(); .
When I run my unit test (using the Apollo to provide a mock mutation) I see the hooked invoked several times, with an output that looks something like this:
console.log src/utils/hooks.js:53
Hook!
console.log src/utils/hooks.js:53
Hook!
console.log src/utils/hooks.js:59
Still loading
console.log src/utils/hooks.js:53
Hook!
console.log src/utils/hooks.js:62
Got cart id guest123
console.log src/utils/hooks.js:53
Hook!
console.log src/utils/hooks.js:53
Hook!
I'm only getting started with hooks, so I'm still having trouble grasping the way they work. Why so many invocations of the hook?
Thank you for your replies!
Think of hooks as having that same code directly in the component. This means that every time the component renders the hook will run.
For example you define:
let cartId;
// ...
if (!cartId || cartId.length === 0) {
createCart();
}
The content inside the statement will run on every render as cartId is created every time and it doesn't have any value assigned at that point. Instead of using if statements use useEffect:
export const useGuestCart = () => {
const [cartId, setCartId] = useState(0);
const [createCart, { data, error, loading }] = useMutation(
MUTATION_CREATE_CART
);
const resetGuestCart = () => {
// function body here
};
useEffect(() => {
if(!cartId || cartId.length === 0){
createCart();
}
}, [cartId]);
useEffect(() => {
// Here we need to consider the first render.
if (loading) {
console.log(`Started loading`);
} else {
console.log(`Finished loading`);
}
}, [loading]);
useEffect(() => {
// Here we need to consider the first render.
console.log(`Got cart id ${data.createEmptyCart}`);
setCartId(data.createEmptyCart);
}, [data]);
return [cartId, resetGuestCart];
};
Also notice that there is no actual benefit from using useCallback if the component which is receiving the function is not memoized.

Categories

Resources