I'm following a react tutorial about handling fetch errors on YouTube. I did exactly what the instructor did but for some reason the catch method is not catching the throw error message. Here's the code:
const Home = () => {
const [blogs, setBlogs] = useState(null);
const [isPending, setIsPending] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
setTimeout(() => {
fetch("http://localhost:8000/blogs")
.then((res) => {
if (!res.ok) {
throw Error("This error is not getting caught");
}
return res.json();
})
.then((data) => {
setBlogs(data);
setIsPending(false);
setError(null);
})
.catch((err) => {
setIsPending(false);
setError(err.message);
});
}, 1000);
}, []);
return (
<div className="home">
{error && <div>{error} </div>}
{isPending && <div>Loading...</div>}
{blogs && <BlogList blogs={blogs} title="All Blogs!" />}
</div>
);
};
export default Home;
Note: the server is not running.
The first .then from fetch will be entered into when the response headers are received. The response headers may indicate that there's a problem - if the response isn't .ok - in which case your throw Error will be entered into as desired and send control flow down to the lower .catch.
But if no response headers are received at all, the first .then will not be entered into - instead, a network error will cause a rejection and the .catch below will be entered into directly.
Your code results in Failed to fetch being displayed, since that's the error message from a failed request that doesn't even get any headers back:
const { useState, useEffect } = React;
const Home = () => {
const [blogs, setBlogs] = useState(null);
const [isPending, setIsPending] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
setTimeout(() => {
fetch("http://localhost:8000/blogs")
.then((res) => {
if (!res.ok) {
throw Error("This error is not getting caught");
}
return res.json();
})
.then((data) => {
setBlogs(data);
setIsPending(false);
setError(null);
})
.catch((err) => {
setIsPending(false);
setError(err.message);
});
}, 1000);
}, []);
return (
<div className="home">
{error && <div>{error} </div>}
{isPending && <div>Loading...</div>}
{blogs && <BlogList blogs={blogs} title="All Blogs!" />}
</div>
);
};
ReactDOM.render(<Home />, document.querySelector('.react'));
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div class='react'></div>
If you wanted to have This error is not getting caught displayed in this sort of situation too, change
setError(err.message);
to
setError('This error is not getting caught');
Related
want to show message to user. console log working fine im not sure why i cant show to user.
tried several methods to fix.
error message from axios = 'Request failed with status code 404'
import { useParams, Link, Route, Routes } from 'react-router-dom';
import { useState, useEffect } from 'react';
import axios from 'axios';
function User() {
const { id } = useParams();
const [user, setUser] = useState(null);
const [error, setError] = useState("");
const [isLoading, setIsLoading] = useState(true);
console.log("error", error);
useEffect(() => {
axios("https://jsonplaceholder.typicode.com/users/" + id)
.then(res => setUser(res.data))
.catch(error => {
console.log("Error=>", error);
console.log(error.message);
setError(typeof (error.message));
console.log("Check",error.message == 'Request failed with status code 404');
})
.finally(() => {
setIsLoading(false);
});
}, [id]);
return (
<div>
{error.message == 'Request failed with status code 404' ? <p>{error.message}</p> :
isLoading ? <h2>Loading...</h2> :
<div>
<h3>User Info</h3>
<p>Name: {user.name}</p>
<p>Email: {user.email}</p>
<p>Phone: {user.phone}</p>
</div>
}
<Link to={`/users/${parseInt(id) + 1}`}><button>Next User</button></Link>
</div>
)
}
export default User
You have a couple bugs:
use === when comparing instead of == to avoid type coercion
when setting state setError(typeof (error.message));, remove typeof of it , setError(error.message);
when testing the error.message simply test error and also display error
Note: I removed <Link> only for demo to stop throwing errors on Routes
import { useParams } from "react-router-dom";
import { useState, useEffect } from "react";
import axios from "axios";
function User() {
const { id } = useParams();
const [user, setUser] = useState(null);
const [error, setError] = useState("");
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
axios("https://jsonplaceholder.typicode.com/users/" + id)
.then((res) => setUser(res.data))
.catch((error) => setError(error.message))
.finally(() => setIsLoading(false));
}, [id]);
return (
<div>
{error === "Request failed with status code 404" ? (
<p>{error}</p>
) : isLoading ? (
<h2>Loading...</h2>
) : (
<div>
<h3>User Info</h3>
<p>Name: {user.name}</p>
<p>Email: {user.email}</p>
<p>Phone: {user.phone}</p>
</div>
)}
</div>
);
}
export default User;
Try to re-organize your logical rendering check the conditions
import { useParams, Link, Route, Routes } from 'react-router-dom';
import { useState, useEffect } from 'react';
import axios from 'axios';
function User() {
const { id } = useParams();
const [user, setUser] = useState(null);
const [error, setError] = useState(null);
const [isLoading, setIsLoading] = useState(true);
console.log("error", error);
useEffect(() => {
axios("https://jsonplaceholder.typicode.com/users/" + id)
.then(res => setUser(res.data))
.catch(error => {
console.log("Error=>", error);
console.log(error.message);
// set error instead of type of the error
setError(error);
console.log("Check",error.message == 'Request failed with status code 404');
})
.finally(() => {
setIsLoading(false);
});
}, [id]);
return (
<div>{loading?
<h2>Loading...</h2>
:
<div>
{error?.message ===null?
<p>'Request failed with status code 404'<p> : <p>{error.message}</p>}
<h3>User Info</h3>
<p>Name: {user.name}</p>
<p>Email: {user.email}</p>
<p>Phone: {user.phone}</p>
</div>
</div>
}
<Link to={`/users/${parseInt(id) + 1}`}><button>Next User</button></Link>
</div>
)
}
export default User
Seeing as you are using empty string in the state, just pass the error.message directly, and then your error will contain a string.
function User() {
const { id } = useParams();
const [user, setUser] = useState(null);
const [error, setError] = useState("");
const [isLoading, setIsLoading] = useState(true);
console.log("error", error);
useEffect(() => {
axios("https://jsonplaceholder.typicode.com/users/" + id)
.then(res => setUser(res.data))
.catch(error => {
console.log("Error=>", error);
console.log(error.message);
setError(error.message); // change this
console.log("Check",error.message == 'Request failed with status code 404');
})
.finally(() => {
setIsLoading(false);
});
}, [id]);
return (
<div>
{/* and change this as well */}
{error ? <p>{error.message}</p> :
isLoading ? <h2>Loading...</h2> :
<div>
<h3>User Info</h3>
<p>Name: {user.name}</p>
<p>Email: {user.email}</p>
<p>Phone: {user.phone}</p>
</div>
}
<Link to={`/users/${parseInt(id) + 1}`}><button>Next User</button></Link>
</div>
)
}
I have a react component in which I am fetching data from the pokeapi. I am trying to render it with Suspense using browser's fetch but I am not able to figure out where I am going wrong.
function FirstPageIndex() {
const [pokemon, setPokemon] = useState({ abilities: [] });
const [loading, setLoading] = useState(false);
console.log("This has been rendered only once");
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
const getDataFunction = async (e) => {
try {
e.preventDefault();
setLoading(true);
const r = await fetch("https://pokeapi.co/api/v2/pokemon/ditto");
const res = await r.json();
await sleep(4000);
console.log("This is the data");
console.log(res);
setPokemon(res);
setLoading(false);
} catch (e) {}
};
return (
<div>
This is coming from the home page
<button onClick={getDataFunction}>Get Data</button>
<Suspense fallback={<h2>Suspense in loading...</h2>}>
{pokemon.abilities.map((ability, i) => (
<p key={i}>
This is my ability {i + 1}: {ability.ability.name}
</p>
))}
</Suspense>
</div>
);
}
I have imported {Suspense} from "react" in this component.
The expected behaviour is when I click on get data, it waits for 3 seconds and then it renders the data, during which time it should show the Suspense's fallback which it is not showing.
EDIT: Updated code to incorporate promise based delay
Please have a look at it?
setTimeout is not a promise so await won't work here.
try doing this
function FirstPageIndex() {
const [pokemon, setPokemon] = useState({ abilities: [] });
const [loading, setLoading] = useState(false);
useEffect(() => {}, []);
console.log("This has been rendered only once");
const getDataFunction = async (e) => {
try {
e.preventDefault();
setLoading(true);
const r = await fetch("https://pokeapi.co/api/v2/pokemon/ditto");
const res = await r.json();
setTimeout(() => {
console.log("This is the data");
console.log(res);
setPokemon(res);
setLoading(false);
}, 3000);
} catch (e) {}
};
if (loading) return <p>This is loading state</p>;
return (
<div>
This is coming from the home page
<button onClick={getDataFunction}>Get Data</button>
<Suspense fallback={<h2>Suspense in loading...</h2>}>
{/* <PokeData resource={pokemon} ></PokeData> */}
{pokemon.abilities.map((ability, i) => (
<p key={i}>
This is my ability {i + 1}: {ability.ability.name}
</p>
))}
</Suspense>
</div>
);
}
I have implemented the following code to fetch data and render a component if everything goes well along with checking loading, error states.
import { useEffect, useState } from "react";
function Posts() {
const [posts, setPosts] = useState([]);
const [loader, setLoader] = useState(false);
const [error, setError] = useState({ status: false, message: "" });
const fetchPosts = () => {
setLoader(true);
setTimeout(async () => {
try {
const response = await fetch(
"https://jsonplaceholder.typicode.com/posts"
);
// throw new Error("Some error occured");
const data = await response.json();
if (data.error) {
setError({ status: true, message: data.error });
} else {
setPosts(data);
}
setLoader(false);
} catch (error) {
console.log("error", error);
setError({ status: true, message: error.message });
setLoader(false);
}
}, 2000);
};
useEffect(() => {
fetchPosts();
}, []);
if (loader) return <h3>Loading...</h3>;
if (error.status) return <h3>Error: {error.message}</h3>;
return (
<div>
<h1>Posts</h1>
{posts.length === 0 && <h3>There are no posts</h3>}
{posts.length > 0 && (
<div>
{posts.map((post) => (
<Post post={post} key={post.id} />
))}
</div>
)}
</div>
);
}
export default Posts;
Is this the right way to handle loading, error and success states when fetching data? or is there a better and more elegant solution than repeating this for every component?
Instead of checking for data.error in the try block, you could check for response.ok; if it is true, call response.json(), otherwise throw an error.
Also move the setLoader call to the finally block to avoid the duplicate calls in try and catch blocks.
try {
const response = await fetch(...);
if (response.ok) {
let data = await response.json();
setPosts(data);
} else {
throw new Error(/* error message */);
}
} catch (error) {
console.log("error", error);
setError({ status: true, message: error.message });
} finally {
setLoader(false);
}
If you want to check for data.error property in a response, you can change the following if condition
if (response.ok) {
to
if (response.ok && !data.error) {
is there a better and more elegant solution than repeating this for
every component?
Make a custom hook to make the fetch request and use that in every component that needs to fetch data from the backend.
const useFetch = (apiUrl, initialValue) => {
const [data, setData] = useState(initialValue);
const [loader, setLoader] = useState(false);
const [error, setError] = useState({ status: false, message: "" });
useEffect(() => {
async function fetchData = (url) => {
setLoader(true);
try {
const response = await fetch(url);
if (response.ok) {
let responseData = await response.json();
setData(responseData);
} else {
throw new Error(/* error message */);
}
} catch (error) {
console.log("error", error);
setError({ status: true, message: error.message });
} finally {
setLoader(false);
}
}
fetchData(apiUrl);
}, [apiUrl]);
return [data, error, loader];
};
Your solution should be good enough to do, but, to me, I would prefer not to set timeout for getting data, and I will use .then and .catch for better readable and look cleaner to me
import { useEffect, useState } from "react";
function Posts() {
const [posts, setPosts] = useState([]);
const [loader, setLoader] = useState(false);
const [error, setError] = useState({ status: false, message: "" });
const fetchPosts = () => {
fetch("https://jsonplaceholder.typicode.com/posts")
.then(response => response.json())
.then(data => {
setPosts(data);
setLoader(false);
})
.catch(error =>{
console.log("error", error);
setError({ status: true, message: error.message });
setLoader(false);
});
};
useEffect(() => {
setLoader(true);
fetchPosts();
}, []);
if (loader) return <h3>Loading...</h3>;
if (error.status) return <h3>Error: {error.message}</h3>;
return (
<div>
<h1>Posts</h1>
{posts.length === 0 && <h3>There are no posts</h3>}
{posts.length > 0 && (
<div>
{posts.map((post) => (
<Post post={post} key={post.id} />
))}
</div>
)}
</div>
);
}
export default Posts;
This isn't really an Apollo question, it's a Javascript promises question, but uses an example from Apollo, because that's the only time I recall seeing it.
Apollo has a React hook that looks like this:
const { loading, error, data } = useQuery(GET_DOGS);
I understand how it returns error -- if the promise resolver throws an error, you get an error back.
I understand how it returns data -- when the promise resolver completes, it returns the data.
But how does it return loading and then later return data? I've coded quite a few node.js promise resolvers and haven't yet seen a pattern that could return loading while the operation is in process, and then later return the data.
What Javascript pattern makes this possible?
They'd use a state variable that starts true and is switched to false when they're done, vaguely like this:
function useQuery(/*...*/) {
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [data, setData] = useState(null);
useEffect(() => {
let cancelled = false;
goGetTheStuff()
.then(data => {
if (!cancelled) {
setData(data);
setLoading(false);
}
})
.catch(error => {
if (!cancelled) {
setError(error);
setLoading(false);
}
});
return () => {
cancelled = true;
};
}, []);
return {loading, error, data};
}
Live Example:
const {useState, useEffect} = React;
function goGetTheStuff() {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() < 0.7) {
// Emulate success
resolve({data: "here"});
} else {
// Emulate failure
reject(new Error("Couldn't get the data"));
}
}, 800);
});
}
function useQuery(/*...*/) {
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [data, setData] = useState(null);
useEffect(() => {
let cancelled = false;
goGetTheStuff()
.then(data => {
if (!cancelled) {
setData(data);
setLoading(false);
}
})
.catch(error => {
if (!cancelled) {
setError(error);
setLoading(false);
}
});
return () => {
cancelled = true;
};
}, []);
return {loading, error, data};
}
function Example() {
const {loading, error, data} = useQuery();
return (
<div>
<div>loading: {JSON.stringify(loading)}</div>
<div>data: {data && JSON.stringify(data)}</div>
<div>error: {error && error.message}</div>
</div>
);
}
ReactDOM.render(<Example/>, document.getElementById("root"));
<div>70% of the time when you run this, the async operation succeeds; 30% of the time, it fails. Run repeatedly if you want to see both scenarios.</div>
<hr>
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js"></script>
I'm newbie in React but I'm developing an app which loads some data from the server when user open the app. App.js render this AllEvents.js component:
const AllEvents = function ({ id, go, fetchedUser }) {
const [popout, setPopout] = useState(<ScreenSpinner className="preloader" size="large" />)
const [events, setEvents] = useState([])
const [searchQuery, setSearchQuery] = useState('')
const [pageNumber, setPageNumber] = useState(1)
useEvents(setEvents, setPopout) // get events on the main page
useSearchedEvents(setEvents, setPopout, searchQuery, pageNumber)
// for ajax pagination
const handleSearch = (searchQuery) => {
setSearchQuery(searchQuery)
setPageNumber(1)
}
return(
<Panel id={id}>
<PanelHeader>Events around you</PanelHeader>
<FixedLayout vertical="top">
<Search onChange={handleSearch} />
</FixedLayout>
{popout}
{
<List id="event-list">
{
events.length > 0
?
events.map((event, i) => <EventListItem key={event.id} id={event.id} title={event.title} />)
:
<InfoMessages type="no-events" />
}
</List>
}
</Panel>
)
}
export default AllEvents
useEvents() is a custom hook in EventServerHooks.js file. EventServerHooks is designed for incapsulating different ajax requests. (Like a helper file to make AllEvents.js cleaner) Here it is:
function useEvents(setEvents, setPopout) {
useEffect(() => {
axios.get("https://server.ru/events")
.then(
(response) => {
console.log(response)
console.log(new Date())
setEvents(response.data.data)
setPopout(null)
},
(error) => {
console.log('Error while getting events: ' + error)
}
)
}, [])
return null
}
function useSearchedEvents(setEvents, setPopout, searchQuery, pageNumber) {
useEffect(() => {
setPopout(<ScreenSpinner className="preloader" size="large" />)
let cancel
axios({
method: 'GET',
url: "https://server.ru/events",
params: {q: searchQuery, page: pageNumber},
cancelToken: new axios.CancelToken(c => cancel = c)
}).then(
(response) => {
setEvents(response.data)
setPopout(null)
},
(error) => {
console.log('Error while getting events: ' + error)
}
).catch(
e => {
if (axios.isCancel(e)) return
}
)
return () => cancel()
}, [searchQuery, pageNumber])
return null
}
export { useEvents, useSearchedEvents }
And here is the small component InfoMessages from the first code listing, which display message "No results" if events array is empty:
const InfoMessages = props => {
switch (props.type) {
case 'no-events':
{console.log(new Date())}
return <Div className="no-events">No results :(</Div>
default:
return ''
}
}
export default InfoMessages
So my problem is that events periodically loads and periodically don't after app opened. As you can see in the code I put console log in useEvents() and in InfoMessages so when it's displayed it looks like this:
logs if events are displayed, and the app itself
And if it's not displayed it looks like this: logs if events are not displayed, and the app itself
I must note that data from the server is loaded perfectly in both cases, so I have totally no idea why it behaves differently with the same code. What am I missing?
Do not pass a hook to a custom hook: custom hooks are supposed to be decoupled from a specific component and possibly reused. In addition, your custom hooks return always null and that's wrong. But your code is pretty easy to fix.
In your main component you can fetch data with a custom hook and also get the loading state like this, for example:
function Events () {
const [events, loadingEvents] = useEvents([])
return loadingEvents ? <EventsSpinner /> : <div>{events.map(e => <Event key={e.id} title={e.title} />}</div>
}
In your custom hook you should return the internal state. For example:
function useEvents(initialState) {
const [events, setEvents] = useState(initialState)
const [loading, setLoading] = useState(true)
useEffect(function() {
axios.get("https://server.ru/events")
.then(
(res) => {
setEvents(res.data)
setLoading(false)
}
)
}, [])
return [events, loading]
}
In this example, the custom hook returns an array because we need two values, but you could also return an object with two key/value pairs. Or a simple variable (for example only the events array, if you didn't want the loading state), then use it like this:
const events = useEvents([])
This is another example that you can use, creating a custom hook that performs the task of fetching the information
export const useFetch = (_url) => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(true);
useEffect(function() {
setLoading('procesando...');
setData(null);
setError(null);
const source = axios.CancelToken.source();
setTimeout( () => {
axios.get( _url,{cancelToken: source.token})
.then(
(res) => {
setLoading(false);
console.log(res.data);
//setData(res);
res.data && setData(res.data);
// res.content && setData(res.content);
})
.catch(err =>{
setLoading(false);
setError('si un error ocurre...');
})
},1000)
return ()=>{
source.cancel();
}
}, [_url])