How do I correctly implement setState() within useEffect()? - reactjs

If this useEffect() runs once (using [] as the second parameter), setTicket(response.data) does not update the value of ticket with data. If I run useEffect() with [ticket] as the parameter, it updates the value of ticket with data, but useEffect becomes an infinite loop.
I need it to run once and update the ticket data. I don't think I understand useEffect() and its second parameter.
What do I do to get the expected result?
import React from "react";
import axios from "axios";
import { useState, useEffect } from "react";
const EditTicket = (props) => {
const [ticket, setTicket] = useState("");
useEffect(() => {
axios
.get("http://localhost:4000/tickets/" + props.match.params.id)
.then((response) => {
setTicket(response.data);
console.log({ ticket });
})
.catch(function (error) {
console.log(error);
});
}, []);
return <div>edit</div>;
};
export default EditTicket;

ticket is a local const. It will never change, and that's not what setTicket is trying to do. The purpose of setTicket is to tell the component to rerender. On that next render, a new local variable will be created, with the new value.
Your code is already written the way it should be written, except that your log statement is not providing you with any useful information. If you want to see that it rerenders with the new value you could move the log statement to the body of the component.
const EditTicket = (props) => {
const [ticket, setTicket] = useState("");
console.log('rendering', ticket);
useEffect(() => {
// same as before

Related

useEffect dependency cause an infinite loop

function Reply({ id, user }) {
const [data, setData] = useState([]);
const [replyText, setReplyText] = useState("");
useEffect(() => {
async function fetchData() {
const response = await _axios.get("/reply/" + id);
setData(response.data);
}
fetchData();
}, [data]); <---- ** problem ** with data(dependency),
infinite request(call) fetchData()
...
}
what's the reason for infinite loop if there's a dependency.
as far as i know, when dependency(data) change, re-render.
but useEffect keep asking for data(axios.get(~~)).
if i leave a comment, i can see normally the latest comments, but the network tab(in develop tools) keeps asking for data(304 Not Modified, under image)
There's an infinite loop because that code says "If data changes, request information from the server and change data." The second half of that changes data, which triggers the first half again.
You're not using data in the callback, so it shouldn't be a dependency. Just remove it:
useEffect(() => {
async function fetchData() {
const response = await _axios.get("/reply/" + id);
setData(response.data);
}
fetchData();
}, []);
// ^^−−−−−−−−−− don't put `data` here
That gives you a blank dependency array, which will run the effect only when the component first mounts. (If you want to run it again after mount, use a different state member for that, or define fetchData outside the effect and use it both in the effect and at the other time you want to fetch data.)
Side note: Nothing in your code is handling rejections from your fetchData function, which will cause "unhandled rejection" errors. You'll want to hook up a rejection handler to report or suppress the error.
You are using setData after the response which causes the data to change and hence the useEffect(() => {<>your code<>} ,[data]) to fire again.
use useEffect(() => {<>your code<>},[]) if you want to execute the AJAX call only once after component mounting
or
use useEffect(() => {<>your code<>}) without the dependency if you want to execute the AJAX call after the component mount and after every update
Dependencies argument of useEffect is useEffect(callback, dependencies)
Let's explore side effects and runs:
Not provided: the side-effect runs after every rendering.
import { useEffect } from 'react';
function MyComponent() {
useEffect(() => {
// Runs after EVERY rendering
});
}
An empty array []: the side-effect runs once after the initial rendering.
import { useEffect } from 'react';
function MyComponent() {
useEffect(() => {
// Runs ONCE after initial rendering
}, []);
}
Has props or state values [prop1, prop2, ..., state1, state2]: the side-effect runs only when any dependency value changes.
import { useEffect, useState } from 'react';
function MyComponent({ prop }) {
const [state, setState] = useState('');
useEffect(() => {
// Runs ONCE after initial rendering
// and after every rendering ONLY IF `prop` or `state` changes
}, [prop, state]);
}

Custom React-native Hook runs the component that calls it twice. I don't understand why

I am trying to learn to work with custom Hooks in React-native. I am using AWS Amplify as my backend, and it has a method to get the authenticated user's information, namely the Auth.currentUserInfo method. However, what it returns is an object and I want to make a custom Hook to both returns the part of the object that I need, and also abstract away this part of my code from the visualization part. I have a component called App, and a custom Hook called useUserId. The code for them is as follows:
The useUserId Hook:
import React, { useState, useEffect } from "react";
import { Auth } from "aws-amplify";
const getUserInfo = async () => {
try {
const userInfo = await Auth.currentUserInfo();
const userId = userInfo?.attributes?.sub;
return userId;
} catch (e) {
console.log("Failed to get the AuthUserId", e);
}
};
const useUserId = () => {
const [id, setId] = useState("");
const userId = getUserInfo();
useEffect(() => {
userId.then((userId) => {
setId(userId);
});
}, [userId]);
return id;
};
export default useUserId;
The App component:
import React from "react";
import useUserId from "../custom-hooks/UseUserId";
const App = () => {
const authUserId = useUserId();
console.log(authUserId);
However, when I try to run the App component, I get the same Id written on the screen twice, meaning that the App component is executed again.
The problem with this is that I am using this custom Hook in another custom Hook, let's call it useFetchData that fetches some data based on the userId, then each time this is executed that is also re-executed, which causes some problems.
I am kind of new to React, would you please guide me on what I am doing wrong here, and what is the solution to this problem. Thank you.
The issue is likely due to the fact that you've declared userId in the hook body. When useUserId is called in the App component it declares userId and updates state. This triggers a rerender and userId is declared again, and updates the state again, this time with the same value. The useState hook being updated to the same value a second time quits the loop.
Bailing out of a state update
If you update a State Hook to the same value as the current state,
React will bail out without rendering the children or firing effects.
(React uses the Object.is comparison algorithm.)
Either move const userId = getUserInfo(); out of the useUserId hook
const userId = getUserInfo();
const useUserId = () => {
const [id, setId] = useState("");
useEffect(() => {
userId.then((userId) => {
setId(userId);
});
}, []);
return id;
};
or more it into the useEffect callback body.
const useUserId = () => {
const [id, setId] = useState("");
useEffect(() => {
getUserInfo().then((userId) => {
setId(userId);
});
}, []);
return id;
};
and in both cases remove userId as a dependency of the useEffect hook.
Replace userId.then with to getUserId().then. It doesn't make sense to have the result of getUserId in the body of a component, since it's a promise and that code will be run every time the component renders.

useEffect causing infinite loop or getting errors

I am trying to learn React hooks. I'm creating a simple news app that leverages the NY times api.
When I leave the dependency empty, nothing loads and when I use data as the dependency it goes into an infinite loop.
When I use isLoading, it works but then I receive an error "localhost/:1 Unchecked runtime.lastError: The message port closed before a response was received." and "localhost/:1 Error handling response: TypeError: Cannot read property 'level' of undefined"
main.js
import React, { useEffect, useState } from "react";
import { nyTimesApi } from "../services/Api";
const Main = () => {
const [isLoading, setIsLoading] = useState(true);
const [data, setData] = useState([]);
const fetchData = async () => {
const result = await nyTimesApi();
setData(result);
setIsLoading(false);
console.log(data.results);
};
useEffect(() => {
fetchData();
}, [isLoading]);
return <div className="main">work</div>;
};
export default Main;
I am also receiving a warning, when using isLoading, in the terminal saying "React Hook useEffect has a missing dependency: 'fetchData'. Either include it or remove the dependency array react-hooks/exhaustive-deps"
What am I doing wrong?
The infinite loop is caused by the combination of using setData(result) and [data]:
The component mounts and the useEffect is run.
setData(result) will asynchronously update the data value and trigger a rerender.
During the rerender the useEffect will be run again as data will not successfully complete the reference comparison.
Repeat 2 to 3.
The warning "React Hook useEffect has a missing dependency" is self explanatory to an extent.
Making use of an external (to the useEffect) variable that is not included in the dependency array may mean that the value of the variable changes and the useEffect will not be retriggered or that the value may not be the expected value.
Below is a an example of how the original snippet might be fixed:
import React, { useEffect, useState } from "react";
import { nyTimesApi } from "../services/Api";
const Main = () => {
const [isLoading, setIsLoading] = useState(true);
const [data, setData] = useState([]);
useEffect(() => {
// Create function inside useEffect so that the function is only
// created everytime the useEffect runs and not every render.
const fetchData = async () => {
const result = await nyTimesApi();
setData(result);
setIsLoading(false);
// setData will update state asynchronously.
// Log the value stored instead.
console.log(result.results);
};
//Run data fetching function.
fetchData();
},
// Both of these are set functions created by useState and will
// not change for the life of the component, but adding them to
// the dependency array will make your linter happy.
// Do not need to check isLoading as it is always true on component
// mount.
[setData, setIsLoading]);
return <div className="main">work</div>;
};
export default Main;
The second argument to useEffect is an array of variables, which trigger the function within useEffect to be called every time they change.
You have [isLoading] as the second argument to useEffect and update the value of this within fetchData(). This is going to cause the useEffect trigger to happen again and again and again.
If you only want to have useEffect call once (in a similar way to ComponentDidMount in class-based components), then you need to specify an empty array as the second argument.
useEffect(() => {
fetchData();
}, []);

React useEffect memory leak, second argument

I am fetching data inside of useEffect, with intention to update useState with data obtained. I kept getting null inside of oneCrypto state value even though console log showed that data was received. Realized it has to do with second argument missing in useState. When add [] empty array, my oneCrypto shows null. When I set [oneCrypto] inside the array, as a dependency, my app crashes - too many requests, console log prints data received over and over and I don't understand why... help please.
import React, { useState, useEffect } from "react"
import { useParams } from "react-router-dom"
export default function SingleCrypto() {
const [loading, setLoading] = useState(false)
const [oneCrypto, setOneCrypto] = useState(null)
const { id } = useParams()
useEffect(() => {
async function getOneCrypto() {
try {
const proxyurl = "https://cors-anywhere.herokuapp.com/";
const response = await fetch(proxyurl +
"https://pro-api.coinmarketcap.com/v1/cryptocurrency/info?id=" +
id,
{
headers: {
}
}
)
const data = await response.json()
const mydata = data.data;
setOneCrypto(mydata)
console.log(oneCrypto)
} catch (error) {
console.log(error)
}
}
getOneCrypto()
}, [oneCrypto])
return <>
<h1>I am Single Crypto page</h1>
</>
}
Calling setOneCrypto causes a rerender and since oneCrypto has changed since the previous render useEffect is called again and the process restarts. Inside the useEffect where you call console.log(oneCrypto) is happening before the value has been updated because the update happens between renders.
Try removing oneCrypto from the array passed in the second argument and call console.log outside your useEffect.
Your loop is:
1) |--> your async function call setOneCrypto ---|
2) |-- new value of oneCrypto call useEffect <--|
Might be you want one request if oneCrypto is null:
!oneCrypto && getOneCrypto();

Updating component state inside of useEffect

I'm playing around w/ hooks for the first time. In my component I make a request for some data inside of useEffect then want to save that data to local state. I try using setUser inside of useEffect but state never gets updated. I've tried it w/o the second argument [] but I get an infinite loop. I know setState is asynchronous and previously you could specify a function as a second argument to setState which would run when state had updated.
I'm wondering what the correct process is for updating state inside of useEffect
import React, { useEffect, useState } from 'react';
import { withRouter } from 'react-router-dom';
import { db } from '../../constants/firebase';
function Profile(props) {
const [ user, setUser ] = useState(null);
const { username } = props.match.params;
useEffect(() => {
db.collection("users").where("username", "==", username)
.get()
.then((querySnapshot) => {
querySnapshot.forEach((doc) => {
console.log(doc.data()); // data comes back fine
setUser(doc.data());
console.log(user); // user is still null
});
})
.catch((error) => {
console.log("Error getting user: ", error);
});
}, [username])
return <div>Profile</div>
}
export default withRouter(Profile);
You can use another useEffect which will run when user updates.
useEffect(() => {
console.log(user);
...
}, [user]);
Try change 2nd argument of useEffect from [username] to [props.match.params.username]

Resources