I'm having two problems my code.
The first is that data is undefined, i'm not fetching data from the API.
The seccond is that the hook useEffect it's not working as it should, like componendidmount, and it's on a loop re rendering the coponent.
this is my code :
import { StatusBar } from 'expo-status-bar';
import React ,{useState,useEffect} from 'react';
import { StyleSheet, Text, View } from 'react-native';
export default function App() {
const [loading, setLoading] = useState(true);
const [users, setUsers] = useState([]);
const fetch = async () =>{
try{
let data = await fetch("https://api.sampleapis.com/coffee/hot", {method: 'GET'});
let json = await data.json();
setUsers(json);
setLoading(false);
}
catch(err){
console.log("Im catching the error");
console.log(err);
setUsers([]);
setLoading(false);
}
}
useEffect(() => {
fetch();
},[])
return (
<View style={styles.container}>
{loading?<><Text>Loading...</Text></>:<Text>Data fetched</Text>}
</View>
);
}
....
const fetch = async () =>{
....
fetch method is calling itself, try to change method name
Related
My useState value of city, default London, isn't working with the fetch value URL being called in my getWeather function. It runs sometimes when I accidentally re-render the code but not any time else
import './App.css';
import Daily from './components/daily';
import {useState, useEffect} from 'react'
function App() {
const [city, setCity] = useState('London')
const [weatherData, setWeather] = useState([])
const getWeather = async() =>{
try{
const resp = await fetch(`https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=f8d957da06af592a1390c360ea801908&units=metric`)
const pResp = await resp.json()
setWeather(pResp)
}
catch{
console.log('error')
}
}
useEffect(()=>{
getWeather()
},[])
return(
<>
<Daily {...weatherData}></Daily>
</>
)
}
export default App;
code here
The problem was that the weatherData was being passed through before it fetched and formatted properly, to fix that I just added a loading screen and render delay so the data is always there
New Code
import './cssdirect/App.css';
import Daily from './components/daily';
import {useState, useEffect} from 'react'
import Loading from './Loading';
function App() {
const [loading, setLoading] = useState(true)
const [city, setCity] = useState('paris')
const [weatherData, setWeather] = useState([])
const getWeather = async() =>{
setLoading(true)
try{
const resp = await fetch(`https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=f8d957da06af592a1390c360ea801908&units=imperial`)
const pResp = await resp.json()
setWeather(pResp)
setLoading(false)
}
catch{
console.log('error')
}
}
useEffect(()=>{
getWeather()
},[])
if(loading==true){
return <Loading/>
}
else{
return <Daily {...weatherData}/>
}
}
export default App;
I've created a custom fetch component, and I'm simply trying to get an image to load on the page from an API called "the dog API". Have I missed something crucial?
App.js
import './App.css';
import './Dog.js';
import useFetch from './useFetch';
function DogApp() {
const API_KEY = "";
const { data, loading, error } = useFetch(`https://api.thedogapi.com/v1/images/search/API_KEY=${API_KEY}`);
if (loading) return <h1>Loading the dogs!</h1>
if (error)console.log(error);
return (
<div className="DogApp">
<img src={data?.url}></img>
</div>
);
}
export default DogApp;
UseFetch.js (hook for fetching the data)
import { useEffect, useState } from 'react';
import axios from "axios";
function useFetch(url) {
const [data, setData] = useState(null); //initialize as null depending on what data is
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
setLoading(true);
axios //make request, if successful it sets data, if not, seterror state
.get(url)
.then((response) => {
setData(response.data);
}).catch((err) => {
setError(err)
}).finally(() => {
setLoading(false);
});
}, [url]);
return {data, loading, error};
}
export default useFetch;
API URL I'm trying to retrieve data from : https://api.thedogapi.com/v1/images/search/
So you're API call (according to the example on thedogapi.com) requires the API key to be set in the header like so:
axios.defaults.headers.common['x-api-key'] = "DEMO-API-KEY"
That fixes the 404, but your code still won't work because the data is returned as an array of objects. So you'll need to map them like so:
{data.map((breed) => (<img src={breed?.url} />))}
I've created a demo sandbox here
I have created Context API I am trying to fetch the data from my API so I can use the state globally, but is not doing it. I am not getting any errors in the console. But when I try to fetch from the Other Component, I am getting data in the console. Just in the Context, I am not getting it.
import React, {useState, useEffect}from 'react'
import ITrucks from '../interface/truck';
import axios from 'axios';
export const TrucksContext= React.createContext({})
export const TrucksProvider:React.FC = ({ children } ) => {
const [isLoading, setIsLoading] = useState(false);
const [trucks, setTrucks] =useState<ITrucks[]>([])
const [isError, setIsError] = useState(false);
const fetchData = () => {
axios
.get('https://localhost:7000/trucks')
.then((response) => {
setIsLoading(false);
setTrucks(response.data);
console.log(response.data)
})
.catch((error) => {
setIsLoading(false);
setIsError(true);
console.log(error);
});
};
useEffect(() => {
fetchData();
}, []);
return (
<TrucksContext.Provider
value={{trucks}}
>
<>
{children}
</>
</TrucksContext.Provider>
);
}
try setTrucks([...response.data])
The problem could be in the API itself. When an API has an error it often return empty data to Axios instead of an error, it depends on the return statement
There is a react-component:
import React, { useState, useCallback } from 'react';
import { useHttp } from '../../hooks/http.hooks';
function Main() {
const {loading, error, request} = useHttp();
const [news, setNews] = useState(0);
const topNews = useCallback(async function() {
const data = await request('http://localhost:5500/api/news/top/2');
return data;
}, []);
console.log(topNews());
return (
<div>Hello world</div>
);
}
export default Main;
And a custom hook:
import { useState } from 'react';
export const useHttp = () => {
const [loading, setLoading] = useState();
const [error, setError] = useState();
async function request(url, { method = 'GET', body = null, headers = {} } = {}) {
setLoading(true);
try {
const response = await fetch(url, { method, body, headers });
const data = await response.json();
if (!response.ok) {
throw new Error(data.msg || 'unhandled error');
}
return data;
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
}
return { loading, request, error }
}
Starting it throw error:
Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.
And so many Promises in console:
As I understood, when loading is changing Main is rendering, because I added a useCallback() but it's not working. How to get rid of looping right?
Inside your React Component, request should be called inside useEffect instead of useCallback. If you keep it that way, the loop looks like this :
Component render
request is called
request update component states
states change force render, so go back to bullet #2
You should change your code to something like this :
import React, { useState, useEffect } from 'react';
import { useHttp } from '../../hooks/http.hooks';
function Main() {
const {loading, error, request} = useHttp();
const [news, setNews] = useState([]);
useEffect(() => {
const data = await request('http://localhost:5500/api/news/top/2');
setNews(data);
}, [])
console.log(news);
return (
<div>Hello world</div>
);
}
export default Main;
See useEffect for more details and advanced usage.
Move the async function call to useEffect, you currently call it on every render:
function Main() {
const {loading, error, request} = useHttp();
const [news, setNews] = useState(0);
useEffect(() => {
async function topNews() {
const data = await request('http://localhost:5500/api/news/top/2');
return data;
}
setNews(topNews());
}, [])
return (
<div>{JSON.stringify(news,null,2)}</div>
);
}
I have a Custom Hook similarly with the below:
import { useEffect, useState } from 'react';
import axios from 'axios';
const myCustomHook = () => {
const [countries, setCountries] = useState([]);
const [isLoading, setLoading] = useState(true);
useEffect(() => {
(async () =>
await axios
.get("MY_API/countries")
.then(response => setCountries(response.data))
.finally(() => setLoading(false)))();
}, []);
return countries;
};
export default myCustomHook;
The hook works great but I am using it in three different areas of my application despite the fact that all the countries are the same wherever the hook is used.
Is there a good pattern to call the axios request just once instead of three times?
EDIT - Final Code After Solution
import { useEffect, useState } from 'react';
import axios from 'axios';
let fakeCache = {
alreadyCalled: false,
countries: []
};
const myCustomHook = (forceUpdate = false) => {
const [isLoading, setLoading] = useState(true);
if (!fakeCache.alreadyCalled || forceUpdate) {
fakeCache.alreadyCalled = true;
(async () =>
await axios
.get("MY_API/countries")
.then(response => setCountries(response.data))
.finally(() => setLoading(false)))();
}
return countries;
};
export default myCustomHook;
One solution to this would be to introduce a custom "cacheing layer" (between your hook and the axios request) that:
caches countries data returned from the first successful request and,
return the same cached data on subsequent requests
There are a number of ways this could be implemented - one possibility would be to define a getCountries() function that implements that cacheing logic in a separate module, and then call that function from your hook:
countries.js
import axios from 'axios';
// Module scoped variable that holds cache data
let cachedData = undefined;
// Example function wraps network request with cacheing layer
export const getCountries = async() => {
// We expect the data for countries to be an array. If cachedData
// is not an array, attempts to populate the cache with data
if (!Array.isArray(cachedData)) {
const response = await axios.get("MY_API/countries");
// Populate the cache with data returned from request
cachedData = response.data;
}
return cachedData;
}
myCustomHook.js
import { useEffect, useState } from 'react';
import { getCountries } from "/countries";
const myCustomHook = () => {
const [countries, setCountries] = useState([]);
const [isLoading, setLoading] = useState(true);
useEffect(() => {
(async() => {
try {
setLoading(true);
// Update hook state with countries data (cached or fresh)
setCountries(await getCountries());
} finally {
setLoading(false)
}
}, []);
});
}
export default myCustomHook;
Each instance of the component runs independently of each other.
Even though the 1st instance state has countries populated the 2nd instance is not aware of it and is still empty.
You could instead consume countries as a prop and if empty invoke the effect, otherwise just return the countries from props.