I'm trying to fetch weather API after setting input onChange and button onClick. It kept returning undefined when I tried to .then (data => {setWeather(data)})
So I applied a solution from a youtube tutorial that uses a conditional operator to display component that goes as below in my App.
This basically worked, but it's so buggy and sensitive that even if I just add a className for some css in WeatherDisplay.jsx it crashes and starts returning undefined, much less any of the other things I need to do to it to get it looking right.
Would be grateful if anyone could tell me why, or even better provide a better alternative?
Edit: It now just no longer works at all and continues to return undefined even when I've reverted it to the code that was working
function handleClick(query) {
fetch(
`https://api.openweathermap.org/data/2.5/weather?q=${query}&units=${unit}&appid=${apiKey}`,
)
.then((response) => response.json())
.then((data) => {
setWeather(data);
console.log(data);
});
setWeatherReturn({
type: weather.weather[0].description,
temp: weather.main.temp,
image: weather.weather[0].icon,
});
}
{
typeof weather.main != "undefined" ? (
<WeatherDisplay
weatherType={weatherReturn.type}
weatherTemp={weatherReturn.temp + "°C"}
weatherImg={`http://openweathermap.org/img/wn/${weatherReturn.image}#2x.png`}
/>
) : (
""
);
}
It looks like you're using setWeatherReturn to process the weather data outside the then chain, so it's likely never loaded at that point.
The simple fix is just moving the setWeatherReturn within that callback:
function handleClick(query) {
fetch(
`https://api.openweathermap.org/data/2.5/weather?q=${query}&units=${unit}&appid=${apiKey}`,
)
.then((response) => response.json())
.then((data) => {
setWeather(data);
setWeatherReturn({
type: data.weather[0].description,
temp: data.main.temp,
image: data.weather[0].icon,
});
});
}
Either way, you shouldn't use state for data derived from state (your weatherReturn), since it's then very easy to have that derived data be out of sync with the state it's derived from (e.g. you call setWeather and forget to call setWeatherReturn); instead use useMemo.
With that in mind, a simple stand-alone component that loads weather information and formats it might look like this.
The idea is that useEffect is automatically triggered when the query or unit changes, and the useMemo similarly when the raw data gets loaded.
If the data isn't yet loaded, the useMemo callback will return undefined, so the component itself just says "loading".
const apiKey = '...';
function WeatherLoader({ query, unit }) {
const [data, setData] = React.useState(null);
React.useEffect(() => {
fetch(
`https://api.openweathermap.org/data/2.5/weather?q=${query}&units=${unit}&appid=${apiKey}`,
)
.then((response) => response.json())
.then(setData);
}, [query, unit]);
const formattedWeatherData = React.useMemo(() => {
if (!data) return undefined;
return {
type: weather.weather[0].description,
temp: weather.main.temp,
image: weather.weather[0].icon,
};
}, [data]);
if (!formattedWeatherData) return <>Loading...</>;
const { type, temp, image } = formattedWeatherData;
return (
<WeatherDisplay
weatherType={type}
weatherTemp={`${weatherReturn.temp}°C`}
weatherImg={`http://openweathermap.org/img/wn/${image}#2x.png`}
/>
);
}
You would use this e.g.
<WeatherLoader query="New York" unit="C" />
(or whatever unit takes as a value).
Related
I have a lot of functions like this, using the same approach, and they all run fine.
But this one is going me crazy.
My code is:
const [match,setMatch] = useState(null);
axios.get(path)
.then((res) => {
status = res.data;
setMatch(res.data)});
const test = match;
If I debug the program and break at setMatch(status), the content of res.data is correct:
res.data is an array, and each element contains a mix of values and objects as show in the screenshot below.
But when I execute the setMatch(status) the result is that the match state variable doesn't change: it remains null as set the first time.
Can someone provide some suggestion?
From your question, I dont know what could likely affect your code from running. But this is the common pattern when handling asynchronous request.
It is expected that it should be executed in the useEffect hook right after it is painted
const Component = () => {
const [match,setMatch] = useState([]);
useEffect(() => {
axios.get(path)
.then((res) => {
status = res.data;
setMatch([match, ...res.data])})
.catch((err) => handleError(err));
}, [path]) // assuming that the path changes otherwise if you are running this once, leave the array empty
return (<div> {(match.length > 0)? 'Loading': {renderYourLogicHere} </div>)
}
Preface: I'm fairly new to React (Coming over from Angular). I know things a similar but different.
I have referenced the following SO threads to no avail in my situation:
React not displaying data after successful fetch
Objects are not valid as a React child. If you meant to render a collection of children, use an array instead
Currently, I'm trying to get my data to display from an API I developed. I'm used to the Angular approach which would call for a ngFor in the template for most data showcase situations.
I'm having trouble wrapping my mind around what I have to do here in order to display my data. The data is expected to be an array of objects which I would then parse to display.
I also receive the following error: Error: Objects are not valid as a React child (found: object with keys {data}). If you meant to render a collection of children, use an array instead.
I've searched high and low for a solution but sadly, nothing I've seen has worked for me. (All of the answers on SO are using the class-based version of React, of which I am not).
You can see my data output in the following screenshot:
I am also including my custom hook code and the component that is supposed to render the data:
CUSTOM DATA FETCH HOOK
interface Drone{
id: number;
name: string;
model: string;
price: number;
}
export function useGetData(urlpath:string) {
const [droneData, setData] = useState<any>()
async function handleDataFetch(path:string){
const result = await fetch(`https://drone-collections-api-jc.herokuapp.com${path}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'x-access-token': 'Bearer API-TOKEN'
}
})
const response = await result.json();
setData(response)
}
useEffect( () => {
handleDataFetch(urlpath)
})
return droneData
}
THE DRONE COMPONENT
import { useGetData } from '../../custom-hooks'
export const Drones = () => {
let data = useGetData('/drones')
console.log(data)
// const DisplayDrone = ( ) => {
// return (
// Array.prototype.map( data => {
// <div>{ data.name }</div>
// })
// )
// }
return (
<div>
<h1>Hello Drones</h1>
</div>
)
}
Also, for more context, the current code can be found at this repo: https://github.com/carter3689/testing-drone-frontend
Please, help me understand what I'm missing. Many Thanks!
There are several locations that needed to be fixed
In fetchData.tsx
export function useGetData(urlpath: string) {
const [droneData, setData] = useState<any>([]);
async function handleDataFetch(path: string) {
const result = await fetch(`https://jsonplaceholder.typicode.com/posts`, {
...
});
const response = await result.json();
setData(response);
}
useEffect(() => {
handleDataFetch(urlpath);
}, []);
Explanation:
you need a "blank" array for looping through. I guess that the error causes by the fact that at the start, before the data is fetched, there is nothing to loop through. It's same as doing undefined.map(), which is obviously fail.
You need a dependencies array for useEffect. Right now your code will do an infinite loop since everytime it get data, it update the state, thus re-run the useEffect and repeat. Add dependencies array limit when that useEffect will run
In Drones.tsx
return (
<div>
{data.map(item => <div>{item.name}</div>}
</div>
)
Not much to say here. I don't use Angular so I'm not sure why you use Array.prototype.map, but in React you can loop through your variable directly. I also have a CodeSandbox link for your project (I use public API)
I am still learning React JS, so, I dont know if this is the way React JS works (so, I am doing it the right way), or I am doing the wrong way, and hence need some help.
I am using a three layer network abstraction for making fetch calls.
First, here is my raw network call making component.
export function useFetch(uri: string, something?: string, getThetoken?: any,
setrequesttype?: string,body? : Object,triggerapi? : string) {
const [data, setData] = useState();
const [error, setError] = useState();
const [loading, setLoading] = useState(true);
//by default we assume a GET request
var requesttype = "GET";
if (setrequesttype !== undefined) {
requesttype = setrequesttype;
}
useEffect(() => {
if (!uri) return;
if (something === "authorized" && !getThetoken) return;
fetch(uri, {
method: requesttype,
headers: {
Authorization: `Bearer ${getThetoken}`,
}
}
)
.then(data => data.json())
.then(setData)
.then(() => setLoading(false))
.catch(setError);
}, [uri, something, getThetoken,requesttype,triggerapi]);
return {
loading,
data,
error
};
}
Here is my middle layer for networking. A middleman between components and the above useFect network calling component.
const Fetch: FC<FetchProps> = ({
uri,
renderSuccess,
loadingFallback = <p>---</p>,
renderError = (error: any) => (
<div>
</div>
),
something,
getThetoken,
setrequesttype,
body,
triggerapi
}: FetchProps) => {
console.log("inside Fetch");
const { loading, data, error } = useFetch(uri, something, getThetoken,setrequesttype,body,triggerapi);
if (loading) return loadingFallback;
if (error) return renderError(error);
if (data) return renderSuccess({ data });
}
export default Fetch;
I built these components, and they work just fine. However, I do run into issues, like this. Here is a component that is able to successfully use it.
const RandomQuote = () => {
const [something, setsomething] = useState("hello");
function changeSomething() {
if (something === "bird") {
setsomething("hello");
} else {
setsomething("bird");
}
}
return (
<Fragment>
<RandomQuoteHelper something={something} />
<Button color="primary" onClick={changeSomething}>
{buttondisplaystring}
</Button>
</Fragment>
);
};
export default RandomQuote;
and here is the RandomQuoteHelper which makes the call to the api, and renders the result.
function RandomQuoteHelper({ something }: RandomQuoteHelperProps) {
//TODO - these things should be coming from a config file
const baseURL = "https://localhost:44372";
const endPoint = "/api/UserNotLoggedIn/GetHoldOfthem";
var url2 = baseURL + endPoint;
//manually set the request type
const setrequesttype = "GET";
return (
<Fetch
uri={url2}
renderSuccess={QuoteDetails}
something={something}
setrequesttype = {setrequesttype}/>
);
}
The above code works, and I get to call my API just fine. Unfortunately, I have to use the function 'changeSomething' to force a state change, which then calls the Fetch/useFetch components.
In the current form, the button click cannot directly call my network components. Not unless a dependent value is forcibly changed. I built it this way, by using a react js book. The author lady, built it this way, which suited her example perfectly. However, now, as I work on an actual project, it does not seem like the best approach. That means, throughout my project, anytime i want to trigger a api call, I have to keep using this 'changeSomething' type setup.
So, is there any way, I can directly call my fetch component?
Ultimate goal is to store the JSON data. That way, if the same github user is sent to the GitHubUser component, instead of making a fresh call to the API, it should load the details from the local storage, preventing a network call.
Key Points about the problem.
do a simple fetch from github public api (no issues, working fine)
store the data to local storage with the github username as key (not working)
retrieve the data from local storage by providing a github username as key (not working)
display json data after render is complete using useEffect (working fine)
I get no errors of any kind with localStorage but nothing gets saved. I have tried this on both Firefox and Edge. The network call happens on every change of login, for the same user, which it should not.
Further, this code is from a textbook I am following, and this is a exact copy from the page that discusses fetch and useEffect. The author goes on to explain that it should work and so far the book has been correct with no errors.
I have put the code in a sandbox here - https://codesandbox.io/s/bold-http-8f2cs
Also, the specific code below.
import React, { useState, useEffect } from "react";
const loadJSON = key =>
key && JSON.parse(localStorage.getItem(key));
const saveJSON = (key, data) =>
localStorage.setItem(key, JSON.stringify(data));
function GitHubUser({ login }) {
const [data, setData] = useState(
loadJSON(`user:${login}`)
);
useEffect(() => {
if (!data) return;
if (data.login === login) return;
const { name, avatar_url, location } = data;
saveJSON(`user:${login}`, {
name,
login,
avatar_url,
location
});
}, [data]);
useEffect(() => {
if (!login) return;
if (data && data.login === login) return;
fetch(`https://api.github.com/users/${login}`)
.then(response => response.json())
.then(setData)
.catch(console.error);
}, [login]);
if (data)
return <pre>{JSON.stringify(data, null, 2)}</pre>;
return null;
}
//Jay-study-nildana
//MoonHighway
export default function App() {
return <GitHubUser login="Jay-study-nildana" />;
}
Note : I get a couple of warnings related to useEffect but I have already isolated that they are not the issue but I dont think they are the problem. it simple tells me not to use a dependency array since there is only one element for both useEffects. I am using the array on purpose.
Update 1
One thing I noticed is, in developer tools, nothing is getting stored in Local Storage after a successfull call to the API. So, right now, I am thinking, saving is not working. Unless I get that working and see the stored data in developer tools, I wont know if load is working or not.
First, if the initial state is the result of some computation, you may provide a function instead, which will be executed only on the initial render:
// instead of this
const [data, setData] = useState(
loadJSON(`user:${login}`)
);
// you better have this
const [data, setData] = useState(() => {
return loadJSON(`user:${login}`);
});
Second, you can achieve what you need with this single useEffect:
const [data, setData] = useState(() => { return loadJSON(`user:${login}`); });
useEffect(() => {
if (!data) {
fetch(`https://api.github.com/users/${login}`)
.then((response) => response.json())
.then((val) => {
saveJSON(`user:${login}`, val); // put data into localStorage
setData(val); // update React's component state
})
.catch(console.error);
}
});
if (data) return <pre>{JSON.stringify(data, null, 2)}</pre>;
return <div>no data</div>;
You will get your data in localStorage. Don't forget that you need to use key user:${login} if you need to get it from there.
I am using React hooks and I need to store the data from my server which is an object, to a state.
This is my state:
const [sendingTime,setSendingTime] = useState({})
This is how I set it inside useEffect:
const getTime= () =>{
axios.get("https://localhost:1999/api/getLastUpdatedTime")
.then(res => {
console.log(res.data)
setSendingTime({sendingTime : res.data})
console.log('sendingTime is coimng',sendingTime)
})
.catch(err => console.error(err))
}
useEffect(() => {
getCount()
getTime()
},[])
Now when I console.log the object state it returns an empty object. Although if I set the object to any variable and then console.log it it doesn't return an empty object. But both ways I am unable to access the properties of the object.
Edit
This is what I was doing previously:
const[time,setTime] = useState({
totalComplaintsTime: '00:00',
resolvedComplaintsTime: '00:00',
unresolvedComplaintsTime:'00:00',
assignedComplaintsTime:'00:00',
supervisorsTime:'00:00',
rejectedComplaintsTime:'00:00'
})
const getTime= () =>{
axios.get("https://localhost:1999/api/getLastUpdatedTime")
.then(res => {
console.log(res.data)
setTime({
totalComplaintsTime: res.data.Complaints[0].updatedAt,
resolvedComplaintsTime: res.data.Resolved[0].updatedAt,
unresolvedComplaintsTime: res.data.Unresolved[0].updatedAt ,
assignedComplaintsTime: res.data.Assigned[0].updatedAt ,
rejectedComplaintsTime: res.data.Rejected[0].updatedAt,
supervisorsTime: res.data.Supervisors[0].updatedAt
})
})
.catch(err => console.error(err))
}
useEffect(() => {
getCount()
// getTime()
getTime()
},[])
And this is how I used the states to set the dynamic values :
Last updated at {time.resolvedComplaintsTime}
This is working perfectly fine but I wanted to store the data in an object state and then access it, which would've been easier and more efficient. Also I wanted to pass this object to another component. That is why I wanted to make a state and store the data in that state.
Solved
So the main problem was accessing the data throughout the component. This is the solution:
sendingTime is being initialized but only when a render occurs. So we add a piece of code to check if that state is initialized or not.
This is where I wanted to display the data.
<div key={sendingTime.length} className={classes.stats}>
<UpdateIcon fontSize={"small"} /> Last updated at{" "}
{Object.keys(sendingTime).length > 0 &&
sendingTime.Complaints[0].updatedAt}
</div>
This way I can access the properties of the object stored in the sendingTime state very easily.
setSendingTime comes from a useState so it is asynchronous:
When you call:
setSendingTime({sendingTime : res.data})
console.log('sendingTime is coimng',sendingTime)
The state sendTime has not been updated, so it display the init value which is {}
The other answers are correct, setState is asynchronous so you will only be able to get sendingTime's new value on the next re-render. And as #Enchew mentions, you probably don't want to set an object as that value most likely.
Try this instead:
const [data, setData] = useState(undefined)
const getTime = () => {
axios.get("https://localhost:1999/api/getLastUpdatedTime")
.then(({ data }) => {
console.log(data)
setData(data)
})
.catch(err => console.error(err))
}
useEffect(() => {
getCount()
getTime()
}, [])
if (!data) return <p>No data...</p>
return (
<>
<p>Complaints: {data.Complaints[0].updatedAt}<p>
<p>Resolved: {data.Resolved[0].updatedAt}<p>
<p>{/* ...etc... */}</p>
</>
)
You should see the value you're expecting to see in the console.log because you're using the locally scoped variable, not the asynchronous value, which will only be updated on the next re-render.
setSendingTime works asynchronously and your object may have not been saved in the state yet. Also pay attention to the way you save the time in your state, you are wrapping the result of the data in another object with property sendingTime, which will result in the following object: sendingTime = { sendingTime: {/*data here */} }. If you are running for sendingTime: {/*data here */} try the following:
const getTime = () => {
axios
.get('https://localhost:1999/api/getLastUpdatedTime')
.then((res) => {
console.log(res.data);
setSendingTime(res.data);
console.log('sendingTime is coimng', sendingTime);
})
.catch((err) => console.error(err));
};
Also have a look at this - how to use hooks with a callback: https://www.robinwieruch.de/react-usestate-callback