I'm using an API to fetch data. When I console.log my data, it shows as an array. But when I try to map over it to get the data to display, it tells me that .map is not a function. I created a custom useFetch hook and then I'm importing it into a separate component. Here's my code and a screenshot of the console.log:
useFetch.js
import { useEffect, useState } from 'react'
function useFetch(url) {
const [data, setData] = useState(null)
const [isLoading, setIsLoading] = useState(true)
const [error, setError] = useState(null)
useEffect(() => {
fetch(url)
.then(response => {
if (!response.ok) {
throw Error("Sorry, couldn't fetch data for this resource!")
}
return response.json()
})
.then(responseData => {
setData(responseData)
setIsLoading(false)
setError(null)
})
.catch(error => {
setIsLoading(false)
setError(error.message)
})
}, [url])
return { data, isLoading, error }
}
export default useFetch
List.js
import React from 'react'
import useFetch from './useFetch'
function PrizeList2017() {
const { data } = useFetch('http://api.nobelprize.org/v1/prize.json?year=2017&yearTo=2017')
return (
<div className="prize-list-2017-container">
<h1>2017</h1>
{data.map(prize => (
<div key={prize.id}>
<h2>{prize.category}</h2>
</div>
))}
{console.log(data)}
</div>
)
}
export default PrizeList2017
console.log
console.log info image
Your help is greatly appreciated!
This data is not present yep when you try to do the map so do:
{data && data.prizes && data.prizes.map(prize => (
Related
I have made a custom hook that takes url and fetches the data in json format. But when I am trying to assign the data into const users using use state, I getting the error :
'Uncaught Error: Too many re-renders. React limits the number of renders to prevent an infinite loop'
Here is the component from where I am trying to assign:
import React, { useState } from "react";
import useFetch from "./fetchData";
import Users from "./Users";
const ASSIGN5 = () => {
const [users, setUsers] = useState();
const { data, isLoading, error } = useFetch(
"https://jsonplaceholder.typicode.com/users"
);
setUsers(data);
return (
<div className="container">
<h1 className="">Users Management App</h1>
{isLoading && <p>Loading users...</p>}
{error && <p>{error}</p>}
<Search onHandleSearch={handleSearch} />
{users && <Users users={users} />}
</div>
);
};
export default ASSIGN5;
And here is the useFetch hook:
import React, { useEffect, useState } from "react";
const useFetch = (url) => {
const [data, setData] = useState([]);
const [isloading, setIsloading] = useState(true);
const [error, setError] = useState();
useEffect(() => {
fetch(url)
.then((res) => {
if (!res.ok) {
throw Error("Fetching unsucessful");
} else {
return res.json();
}
})
.then((data) => {
setData(data);
setIsloading(false);
setError(null);
})
.catch((error) => {
setError(error.message);
setIsloading(false);
});
}, [url]);
return { data, isloading, error };
};
export default useFetch;
But it runs fine when I use data directly without assigning but I need to because have to filter the data using functions
I am expecting that the data will assigned to the const users
Don't call state setters unconditionally in the component body or that'll trigger infinite renders.
It appears you don't need the users state at all because it's just an alias of the data array returned by your useFetch hook.
const ASSIGN5 = () => {
const { data, isLoading, error } = useFetch(
"https://jsonplaceholder.typicode.com/users"
);
return (
<div className="container">
<h1 className="">Users Management App</h1>
{isLoading && <p>Loading users...</p>}
{error && <p>{error}</p>}
<Search onHandleSearch={handleSearch} />
{data?.length && <Users users={data} />}
</div>
);
};
If you want to rename it you can use
const { data: users, isLoading, error } = useFetch(...);
// now you can replace `data` with `users`
Search and handleSearch weren't defined but I assume those are in your actual code somewhere.
Components are typically PascalCase, so ASSIGN5 should be Assign5.
I am trying to use react to fetch api information (see below) but it is not returning any values but works when I use a different link. Not sure what the issue is with the second link. How do I make the second link work or is there a different method?
import React, { useState, useEffect } from "react";
import axios from "axios";
export default function App() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
getData()
}, []);
async function getData() {
// await axios("https://randomuser.me/api") // <----- this works
await axios("https://api.sampleapis.com/wines/reds") // <----- this does not work
.then((response) => {
setData(response.data);
console.error("No Error fetching data: fds");
})
.catch((error) => {
console.error("Error fetching data: ", error);
setError(error);
})
.finally(() => {
setLoading(false);
});
}
if (loading) return "Loading...";
if (error) return "Error!"; //dfdsaf
return ( <>
{/* <img src={data.results[0].picture.medium} alt="random user" /> */}
<pre>{JSON.stringify(data, null, 2)}</pre>
</>
); }
The structures of the returning data from each APIs are different from each other.
https://randomuser.me/api -> returns {"results":[{"gender"...
https://api.sampleapis.com/wines/reds -> returns [{"winery":"Maselva","wine"...
So obviously just changing the URL won't work. You have to change the code accordingly to the structure of the returned data to access them properly.
The response structure is different and you may not need to use .then and .catch as you have already used Async/Await.
Here's a working example in codesandbox
<script type="text/babel">
// import React, { useState, useEffect } from "react";
// import axios from "axios";
const { useState, useEffect } = React;
function App() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [randomUserData, setRandomUserData] = useState(null);
const [randomUserLoading, setRandomUserLoading] = useState(true);
const [randomUserError, setRandomUserError] = useState(null);
useEffect(() => {
getRandomUserData();
getSampleApiData();
}, []);
async function getSampleApiData() {
try {
const response = await axios("https://api.sampleapis.com/wines/reds");
setData(response.data);
console.error("No Error fetching data: fds");
} catch (error) {
console.error("Error fetching data: ", error);
setError(error);
}
setLoading(false);
}
async function getRandomUserData() {
try {
const response = await axios("https://randomuser.me/api");
setRandomUserData(response.data);
console.error("No Error fetching data: fds");
} catch (error) {
console.error("Error fetching data: ", error);
setRandomUserError(error);
}
setRandomUserLoading(false);
}
if (loading || randomUserLoading) return "Loading...";
if (error || randomUserError) return "Error!";
return (
<div
style={{
display: "grid",
gridTemplateColumns: "repeat(2, 1fr)",
width: "100%"
}}
>
<div>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
<div>
<pre>{JSON.stringify(randomUserData, null, 2)}</pre>
</div>
</div>
);
}
// export default App;
ReactDOM.render(<App />, document.querySelector("#app"));
</script>
<div id="app"></div>
<script crossorigin src="https://unpkg.com/react#17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#17/umd/react-dom.development.js"></script>
<script crossorigin src="https://unpkg.com/axios#0/dist/axios.js"></script>
<script crossorigin src="https://unpkg.com/#babel/standalone#7/babel.js"></script>
I am trying to make work search input. I'm filtering through fetched data in useEffect in Hooks/useCountries component, listening to input in App.js and passing props for handleChange in Searchbar component. Something is missing, I can't figure out what. Here is the link of codesandbox and Hooks/useCountries component
import React, { useState, useEffect } from "react";
export default function useCountries(search) {
const [data, setData] = useState([]);
const [error, setError] = useState(null);
const fetchData = () => {
fetch("https://restcountries.eu/rest/v2/all")
.then((res) => res.json())
.then((result) => setData(result))
.catch((err) => console.log("error"));
};
useEffect(() => {
const searchResult =
data &&
data
.filter((item) => item.name.toLowerCase().includes(search))
.map((element) => <div>{element.name}</div>);
}, []);
useEffect(() => {
fetchData();
}, []);
return [data, error];
}
App.js
import React, { useState } from "react";
import SearchBar from "./components/SearchBar";
import useCountries from "./Hooks/useCountries";
import MainTable from "./components/MainTable";
import "./App.scss";
export default function App() {
const [search, setSearch] = useState("");
const [data, error] = useCountries(search);
const handleChange = (e) => {
setSearch(e.target.value);
};
return (
<div className="App">
<SearchBar handleChange={handleChange} search={search} />
<MainTable countries={data} />
</div>
);
}
SearchBar component
import React, { useState } from "react";
import "./SearchBar.scss";
export default function Searchbar({ handleChange, search }) {
return (
<div className="SearchBar">
<input
className="input"
type="text"
placeholder="search country ..."
value={search}
onChange={handleChange}
/>
</div>
);
}
So in your useCountries hook, you need to update the useEffect to trigger whenever search is changed. Otherwise, it runs when the hook is first loaded, but then never again. I'm also not exactly sure what your logic is attempting to accomplish in your current useEffect. I've posted a possible update to it that also changes your search to regex to account for the possibility that the user may not be typing in lower case. Let me know if this doesn't work for your use case and I can adapt it.
import React, { useState, useEffect } from "react";
export default function useCountries(search) {
const [data, setData] = useState([]);
const [error, setError] = useState(null);
const [searchResults, setSearchResults] = useState(null);
const fetchData = () => {
fetch("https://restcountries.eu/rest/v2/all")
.then((res) => res.json())
.then((result) => setData(result))
.catch((err) => console.log("error"));
};
useEffect(() => {
if (search) {
const searchCriteria = new RegExp(search, "i");
setSearchResults(
data
.filter((item) => searchCriteria.test(item.name))
.map((element) => <div>{element.name}</div>)
);
} else {
setSearchResults(null);
}
}, [search]);
useEffect(() => {
fetchData();
}, []);
return [data, error, searchResults];
}
And in App.js add:
const [data, error, searchResults] = useCountries(search);
Here is the fork off of your sandbox where this works:
CodeSandbox
Im having troubles rendering components based on api calls in React. I fetch my data in useEffect hook update a state with the data. The state is null for a while before the api get all the data but by that time, the components are rendering with null values. This is what I have:
import React, { useEffect, useState } from 'react'
import axios from 'axios';
import { Redirect } from 'react-router-dom';
const Poll = (props) => {
const [poll, setPoll] = useState(null);
//if found is 0 not loaded, 1 is found, 2 is not found err
const [found, setFound] = useState(0);
useEffect(() => {
axios.get(`api/poll/${props.match.params.id}`)
.then(res => {
console.log(res.data);
setPoll(res.data);
setFound(1);
})
.catch(err => {
console.log(err.message);
setFound(2);
});
}, [])
if(found===2) {
return(
<Redirect to="/" push />
)
}else{
console.log(poll)
return (
<div>
</div>
)
}
}
export default Poll
That is my workaround but it doesnt feel like thats the way it should be done. How can I set it so that I wait for my api data to get back then render components accordingly?
You don't need to track the state of the API call like const [found, setFound] = useState(1). Just check if poll exists and also you can create a new state variable for tracking the error.
For example if (!poll) { return <div>Loading...</div>} this will render a div with 'loading...' when there is no data. See the code below, for complete solution,
import React, { useEffect, useState } from 'react'
import axios from 'axios';
import { Redirect } from 'react-router-dom';
const Poll = (props) => {
const [poll, setPoll] = useState(null);
const [hasError, setHasError] = useState(false);
useEffect(() => {
axios.get(`api/poll/${props.match.params.id}`)
.then(res => {
console.log(res.data);
setPoll(res.data);
})
.catch(err => {
console.log(err.message);
setHasError(true)
});
}, [])
if(!poll) {
console.log('data is still loading')
return(
<div>Loading....</div>
)
}
if (hasError) {
console.log('error when fetching data');
return (
<Redirect to="/" push />
)
}
return (
<div>
{
poll && <div>/* The JSX you want to display for the poll*/</div>
}
</div>
);
}
export default Poll
In your than, try to use a filter:
setPoll(poll.filter(poll => poll.id !== id));
Make sure to replace id by your identificator
The standard way is to have other variables for the loading and error states like this
const Poll = (props) => {
const [poll, setPoll] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(false);
useEffect(() => {
setLoading(true);
axios.get(`api/poll/${props.match.params.id}`)
.then(res => {
console.log(res.data);
setPoll(res.data);
})
.catch(err => {
console.log(err.message);
setError(true);
})
.finally(()=> {
setLoading(false);
};
}, [])
if(error) return <span>error<span/>
if(loading) return <span>loading<span/>
return (
<div>
// your poll data
</div>
)
}
I am not able to find where is the issue with this custom hook?
import { useState, useEffect } from "react";
const SAMPLE_DATA_URL = "../feed/sample.json";
const useFetch = () => {
const [response, setResponse] = useState(null);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
const doFetch = async () => {
setLoading(true);
await fetch(SAMPLE_DATA_URL)
.then(res => res.json())
.then(jsonData => {
setResponse(jsonData);
})
.catch(err => setError(err))
.finally(() => setLoading(false));
};
doFetch();
},[]);
return { response, error, loading };
};
export default useFetch;
on network tab I can see 200 OK but the preview is saying "You need to enable JavaScript to run this app." and also the response is html of my index screen. I checked javascript in browser is allowed and the json file is a valid json.
on return object I am getting error: true
Where its been used
import React from "react";
import styles from "./Series.module.css";
import { TitleBar } from "../../atoms";
import {useFetch} from '../../utils';
const Series = () => {
const { response, loading, error } = useFetch();
return (
<div >
<TitleBar>Popular Series</TitleBar>
<div className={styles.content}>
{loading && <p>Loading...</p>}
{error && <p>Oops, Something went wrong...</p>}
{response && <p>response</p>}
</div>
</div>
);
};
export default Series;
If you are using CRA, you can put your sample.json inside your public folder and so you can fetch the URL directly:
fetch("sample.json")
.then(...)
.then(...)
Although, you don't need to do all that as you can just import the data like any other js modules
import data from "./sample.json"; // Path
const App = () => {
return (
<div className="App">
{data.map(item => {
// return JSX with item...
})}
</div>
);
};
codesandbox examples.