Re-calling react hook "useEffect" problem - reactjs

I am working on a new small project in React and I am using React hooks. The ultimate target of this app is to fetch the data of the weather of a certain city from openweather API and display it on the screen. I have created a custom hook to fetch the data from the endpoint and passed in three arguments as shown below :
export const useHttp = (baseURL, dependancies, isSubmit) => {
// Inizialize isLoading to false
const [isLoading, setLoading] = useState(false);
// Initialize the fetched data to an empty string
const [fetchedData, setFetchedData] = useState('');
useEffect(() => {
/*Check if isSubmit is true before fetching the corresponding
data*/
if (isSubmit) {
// set isLoading to true until we get the data
setLoading(true);
// Start fetching the data from the url received
fetch(baseURL)
.then(response => {
if (!response.ok) {
throw new Error('Failed to fetch. ');
}
return response.json();
})
// Return the data when fetched successfully
.then(data => {
setLoading(false);
setFetchedData(data);
})
/*Show an alert when fetching encounters an error and stop the
loader accordingly*/
.catch(err => {
alert("Please insert a valid name")
setLoading(false);
})
}
}, dependancies)
// Returning the data to use them later in displaying the weather
return [isLoading, fetchedData];
};
And here is how my form component works :
// initialized the input to an empty string
const [searchTerm, setSearchTerm] = useState('');
// Initialize the state of submit to false
const [isSubmit, setIsSubmit] = useState(false);
// Use array destruction to get isLoading and fetchedData from the imported userHttp hook
const [isLoading, fetchedData] = useHttp(`http://api.openweathermap.org/data/2.5/weather?q=${searchTerm}
&APPID=b8c1572d189de60f5480324c6b53d9ab`, [isSubmit], isSubmit);
// Use object destruction to get the desired properties out of the fetched data
const { name, sys, weather, main } = fetchedData ? fetchedData : '';
// Get the user input in the search bar to pass it to submitInput function
const getSearchTerm = (e) => {
setSearchTerm(e.target.value);
}
// Submit the userinput and call the custom hook to fetch the data matched with the input
const submitInput = (event) => {
// Prevent the form from actually submitting
event.preventDefault();
// Change the state of isSubmit so that useEffect can be re-called
setIsSubmit(!isSubmit);
}
As you can see, I wanted to change the value of the state "isSubmit" whenever the user submits in order to recall useEffect as "isSubmit" is also passed as a dependency. Moreover, I created a condition to prevent useEffect from working whenever the app is rendered because I want it to work only when the user submits.
The thing is, it works perfectly the first time but when I enter another value, I have to click twice on the button to make it work. I spent a while thinking about this issue but I came to nothing in the end. Hopefully, someone can help me with this. Thanks in advance.
Here is also a link to the project rep on GitHub :
https://github.com/Saifsamirk/weatherApp

Your useEffect hook only fires when isSubmit = true. When you call submitInput you only change the value of isSubmit to !isSubmit. It will only be true every second time. You might wanna reset your isSubmit state to false after firing the event.

Related

usestate can change the state value after axios in useEffect

I expected to get the url with category=business,but the web automatically reset my state to the url that dosent have the category.I dont know the reason behind
let {id}=useParams()
const [newsurl,setNewsurl]=useState(()=>{
const initialstate="https://newsapi.org/v2/top-headlines?country=us&apiKey=c75d8c8ba2f1470bb24817af1ed669ee"
return initialstate;})
//console.log(id);
const [articles, setActicles] = useState([]);
useEffect( ()=>{
if(id === 2)
console.log("condition")
setNewsurl("https://newsapi.org/v2/top-headlines?country=de&category=business&apiKey=c75d8c8ba2f1470bb24817af1ed669ee")},[])
useEffect(() => {
const getArticles = async () => {
const res = await Axios.get(newsurl);
setActicles(res.data.articles);
console.log(res);
};
getArticles();
}, []);
useEffect(() => {
console.log(newsurl)
// Whatever else we want to do after the state ha
s been updated.
}, [newsurl])
//return "https://newsapi.org/v2/top-headlines?country=us&apiKey=c75d8c8ba2f1470bb24817af1ed669ee";}
return (<><Newsnavbar />{articles?.map(({title,description,url,urlToImage,publishedAt,source})=>(
<NewsItem
title={title}
desciption={description}
url={url}
urlToImage={urlToImage}
publishedAt={publishedAt}
source={source.name} />
)) } </>
)
one more things is that when i save the code the page will change to have category but when i refresh it ,it change back to the inital state.Same case when typing the url with no id.May i know how to fix this and the reason behind?
Setting the state in React acts like an async function.
Meaning that the when you set the state and put a console.log right after it, it will likely run before the state has actually finished updating.
You can instead, for example, use a useEffect hook that is dependant on the relevant state in-order to see that the state value actually gets updates as anticipated.
Example:
useEffect(() => {
console.log(newsurl)
// Whatever else we want to do after the state has been updated.
}, [newsurl])
This console.log will run only after the state has finished changing and a render has occurred.
Note: "newsurl" in the example is interchangeable with whatever other state piece you're dealing with.
Check the documentation for more info about this.
setState is an async operation so in the first render both your useEffetcs run when your url is equal to the default value you pass to the useState hook. in the next render your url is changed but the second useEffect is not running anymore because you passed an empty array as it's dependency so it runs just once.
you can rewrite your code like the snippet below to solve the problem.
const [articles, setActicles] = useState([]);
const Id = props.id;
useEffect(() => {
const getArticles = async () => {
const newsurl =
Id === 2
? "https://newsapi.org/v2/top-headlines?country=de&category=business&apiKey=c75d8c8ba2f1470bb24817af1ed669ee"
: "https://newsapi.org/v2/top-headlines?country=us&apiKey=c75d8c8ba2f1470bb24817af1ed669ee";
const res = await Axios.get(newsurl);
setActicles(res.data.articles);
console.log(res);
};
getArticles();
}, []);

How can I stop react from making multiple network request simultaneously

I am making a get request to get data from my rest API, and when I have the data react will keep on making the same request simultaneously multiple times.
this is the code:
export default function Transaction() {
const [transactions, setTransaction] = useState([]);
const [loading, setLoading] = useState(true)
const { id } = useParams();
// this is where I am getting the problem
useEffect(() => {
const fetchTransc = async () => {
const res = await axios.get(`http://localhost:4000/api/get-records/${id}`);
setTransaction(res.data)
setLoading(false)
console.log(res.data)
};
fetchTransc();
},[id,transactions]);
The second argument of the UseEffect hook is the dependency array. It tells react which variables to watch for changes. When a dependency changes or when the component is mounted for the first time, the code inside the hook is executed.
In your case the array indicates that each time “id” or “transations” change, the hook should be executed.
Since setTransation is called in the fetch function, that will trigger the hook again because “transations” is present in hook’s the dependency array.
Each time the transactions state variable is set with a brand a new object fetched from the url, react will trigger the useEffect hook.
If “transations” is removed from the hook’s dependency array, this should work fine. Maybe also adding an IF to check the “id” value could be useful to prevent errors.
useEffect(() => {
const fetchTransc = async () => {
if(id != null) {
const res = await axios.get(`http://localhost:4000/api/get-records/${id}`);
setTransaction(res.data)
setLoading(false)
console.log(res.data)
}
};
fetchTransc();
},[id]);

I can only get the API parameters, after I press CTRL+S or update my file

I'm having a problem, I made an API call to find out the city of the user who accessed my application.
So far so good, I can show on the screen the city the user is in, but only after I press the CTRL + S keys (save my file) then the API GET works. Same in the example below
When loading the application:
After I save the application in my code editor:
Can you tell me how I can make the location appear immediately without me having to save my code?
Below is my code for this component
Location.tsx
import axios from "axios";
import { useEffect, useState } from "react"
export function Location(){
const API_endpoint = `https://api.openweathermap.org/data/2.5/weather?`
const API_key = `e3b1d84b18bc5ef312403e0caf94b698`
const [latitude, setLatitude] = useState<number>(0);
const [longitude, setLongitude] = useState<number>(0);
const [cityName, setCityName] = useState<string>('')
const [isFetching,setIsFetching] = useState<boolean>(true);
const [error, setError] = useState<Error | null>(null)
useEffect(() => {
navigator.geolocation.getCurrentPosition((position) => {
setLatitude(position.coords.latitude)
setLongitude(position.coords.longitude)
})
let finalApiEndPoint = `${API_endpoint}lat=${latitude}&lon=${longitude}&exclude=hourly,daily&appid=${API_key}`
axios.get(finalApiEndPoint)
.then((response) =>{
setCityName(response.data.name)
console.log(response.data.name)
})
.catch(err => {
setError(err)
})
.finally(() => {
setIsFetching(false)
})
}, [])
return(
<>
<div>
<h1>
<>
Your Location: {cityName}
</>
</h1>
</div>
</>
)
}
The issue is a combination of:
Asynchronous code trying to use a result before it is received
Synchronous usage of React state after change, before the change takes effect (next render)
1. Using result from asynchronous code
In the useEffect, the call to navigator.geolocation.getCurrentPosition properly uses its result within a callback, because it will take some time before the gelocation is known.
So anything that depends on this data should also be executed only after that data is received, i.e. typically within said callback. Or through the states where you store the geolocation results for decoupling.
2. React state changes on next render
However, when setting a new React state value, that state still holds the previous value for the rest of this render pass: any synchronous code after setLatitude will still see the old value of latitude, until next render (it is a const, after all).
So either use directly position.coords.latitude (which shows that it must be executed within the callback), or push the decoupling further by using a separate useEffect, so that the API call is executed only once the states have actually changed their value.
const [latitude, setLatitude] = useState<number>(0);
const [longitude, setLongitude] = useState<number>(0);
const [cityName, setCityName] = useState<string>('')
useEffect(() => {
navigator.geolocation.getCurrentPosition((position) => {
setLatitude(position.coords.latitude)
setLongitude(position.coords.longitude)
})
}, []) // Get geolocation only once
useEffect(() => {
let finalApiEndPoint = `${API_endpoint}lat=${latitude}&lon=${longitude}&exclude=hourly,daily&appid=${API_key}`
axios.get(finalApiEndPoint)
.then((response) =>{
setCityName(response.data.name)
console.log(response.data.name)
})
}, [latitude, longitude]) // Call API whenever lat or long change
The component suddenly displays the expected state value after saving your file, because it triggers a hot replacement. The latter preserves the state (hence the latitude and longitude of first geolocation), but the new component is re-mounted, which triggers a new execution of the useEffect, where the API call is re-made, using the preserved (now correct) states.

ReactJS delay update in useState from axios response

I am new to react js and I am having a hard time figuring out how to prevent delay updating of use state from axios response
Here's my code:
First, I declared countUsername as useState
const [countUsername, setUsername] = useState(0);
Second, I created arrow function checking if the username is still available
const checkUser = () => {
RestaurantDataService.checkUsername(user.username)
.then(response => {
setUsername(response.data.length);
})
.catch(e => {
console.log(e);
})
}
So, every time I check the value of countUsername, it has delay like if I trigger the button and run checkUser(), the latest response.data.length won't save.
Scenario if I console.log() countUseranme
I entered username1(not available), the value of countUsername is still 0 because it has default value of 0 then when I trigger the function once again, then that will just be the time that the value will be replaced.
const saveUser = () => {
checkUser();
console.log(countUsername);
}
Is there anything that I have forgot to consider? Thank you
usually there is a delay for every api call, so for that you can consider an state like below:
const [loading,toggleLoading] = useState(false)
beside that you can change arrow function to be async like below:
const checking = async ()=>{
toggleLoading(true);
const res = await RestaurantDataService.checkUsername(user.username);
setUsername(response.data.length);
toggleLoading(false);
}
in the above function you can toggle loading state for spceifing checking state and disable button during that or shwoing spinner in it:
<button onClick={checking } disabled={loading}>Go
i hope this help
.then is not synchronous, it's more of a callback and will get called later when the api finishes. So your console log actually goes first most of the time before the state actually saves. That's not really something you control.
You can do an async / await and return the data if you need to use it right away before the state changes. And I believe the way state works is that it happens after the execution:
"State Updates May Be Asynchronous" so you can't really control when to use it because you can't make it wait.
In my experience you use the data right away from the service and update the state or create a useEffect, i.g., useEffect(() => {}, [user]), to update the page with state.
const checkUser = async () => {
try {
return await RestaurantDataService.checkUsername(user.username);
} catch(e) {
console.log(e);
}
}
const saveUser = async () => {
const user = await checkUser();
// do whatever you want with user
console.log(user);
}

Not awaiting for data in useEffect

I have a useEffect in my component that is waiting for data from the context so that it can set it in state. But its not waiting for the state and is moving on to the next line of code to set isLoading to false.
I'd like it to wait for the data so that I can render the loading.
I tried setting the isFetchingData in the context but I had run into problems where if another component calls it first it would set the isFetchingData state to false.
First call to ReactContext is setting the isLoading sate to false
It is fine for results to come back with no records. The component would render 'No records found'. Therefore, I cannot check the length on state to say if length is zero then keep loading.
Following is my code:
Context
const [activeEmployees, setActiveEmployees] = useState([]);
const [terminatedEmployees, setTerminatedEmployees] = useState([]);
useEffect(() => {
getEmployees()
.then(response => {
/// some code...
setActiveEmployees(response.activeEmployees)
setTerminatedEmployees(response.terminatedEmployees)
});
});
Component
const EmployeesTab = () => {
const { activeEmployees, terminatedEmployees } = useContext(BlipContext);
//Component states
const [isFetchingData, setIsFetchingData] = useState(true);
const [newEmployees, setNewEmployees] = useState([]);
const [oldEmployees, setOldEmployees] = useState([]);
useEffect(() => {
async function getData() {
await setNewEmployees(activeEmployees);
await setOldEmployees(terminatedEmployees);
setIsFetchingData(false);
}
getData();
}, [activeEmployees, terminatedEmployees, isFetchingData]);
if(isFetchingData) {
return <p>'Loading'</p>;
}
return (
// if data is loaded render this
);
};
export default EmployeesTab;
Since you have useState inside your useContext, I don't see the point of storing yet again the activeEmployees in another state.
If you want to have a local loading variable it could something like:
const loading = !(activeEmployees.length && terminatedEmployees.length);
This loading will update whenever getEmployees changes.
And to answer you question, the reason await is not having an effect is because setState is synchronous.

Resources