I know that when passing an empty array to useState hook we should also provide the proper type, otherwise typescript would infer never[].
The following is something I did before knowing that and by my surprise is working:
It's a component intended to fetch some data, that data is of type any and will be stored in loadedUsers, which has a type of never[].
const AllUsers: FC<AllUsersProps> = () => {
const [isLoading, setIsLoading] = useState(true);
const [loadedUsers, setLoadedUsers] = useState([]);
useEffect(() => {
UserService.getAllUsers()
.then((response) => response.json())
.then((data) => {
console.log("users after retrieving data", data);
setIsLoading(false);
setLoadedUsers(data);
});
}, []);
Then I use loadedUsers to render a simple view
if (isLoading) {
return (
<section>
<p>Loading users</p>
</section>
);
}
return (
<div className="row">
<div className="col">
<h3>Users table</h3>
<UserTable users={loadedUsers} />
</div>
<div className="col">
<h3>Find user by id</h3>
<UserSearch />
</div>
</div>
);
};
Why am I not getting any errors during compilation? even in vscode there is no warning message
Related
so i'm trying to implement a custom react hook for fetch. It's working fine, but i can't seem to do it with the errors. if i try to display the error in a custom component it's says object is not a valid React child ... okey i know that, but how then it's working when there's no error in the componenet ? Here's the code:
Hook:
import { useEffect, useState } from "react";
const useFetch = (url) => {
const [data, setData] = useState([]);
const [error, setError] = useState(false);
const [errorMessage, setErrorMessage] = useState('');
useEffect(() => {
const controller = new AbortController();
const signal = controller.signal;
fetch(url, { signal: signal })
.then(res => res.json())
.then(result => {
setData([result]);
})
.catch(err => {
setError(true);
setErrorMessage(err);
})
return () => {
setError(false);
controller.abort();
};
}, [url]);
return { data, error, errorMessage };
};
export default useFetch;
Component:
const WeatherNow = () => {
const { city } = useContext(CityContext);
import ErrorHandler from '../error-component/ErrorHandler';
const { data, error, errorMessage } = useFetch(`https://api.weatherapi.com/v1/forecast.json?key=${process.env.REACT_APP_API_KEY}&q=${city}&aqi=no`);
if (error) {
return <>
<ErrorHandler props={errorMessage} />
</>
};
return (
<>
{data && data.map(x => (
<div className="today-forecast" key={city}>
<h4>
{city}
</h4>
<div>
{x.current.condition.text}
</div>
<div>
<img src={x.current.condition.icon} alt='' />
</div>
<h3>
{x.current.feelslike_c} *C
</h3>
<h5 className='rain-wind'>
Rain: {x.current.precip_in} % / Wind: {x.current.wind_kph} km/h
</h5>
<div className='links'>
<Link to='/hourly'>Hourly</Link> <Link to='/daily'>Daily</Link>
</div>
</div>
))}
</>
);
};
The ErrorHandler:
import './ErrorHandler.css';
import error from './error.png';
const ErrorHandler = ({ props }) => {
return (
<div className="error-component">
<div>
<h4>
{props}
</h4>
</div>
<div>
<img src={error} />
</div>
</div>
);
};
export default ErrorHandler;
Because of the catch (err) is an unknown type, it might return anything and more likely an object with a message key.
Try to change the way you are setting the error message and make sure it’s a string:
setErrorMessage(typeof err?.message === "string" ? err.message : "Unknown Error");
Warning
Using process.env.REACT_APP_API_KEY in client side is not safe at all.
The problem is in the catch block , somehow the err is inpromise and i can't use it
I have a simple function component, where I am making a .get() call and then later in my jsx I am just mapping through using .map() an array to print data.
But for some reason, the array is not recognizable and it is not printing the data.
useEffect() is working fine. It is logging out data. But, the books array is just not working and I'm unable to figure out why
below is the code.
const [books, setBooks] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState();
useEffect(() => {
setLoading(true);
axios
.get("database.json")
.then((response) => {
const request = response.data.results.books;
console.log("request", request);
setBooks(books);
})
.catch(function (error) {
// handle error
console.log(error);
})
.finally(() => {
setLoading(false);
});
}, []);
if (loading) {
return <p>Data is loading...</p>;
}
if (error || !Array.isArray(books)) {
return <p>There was an error loading your data!</p>;
}
return (
<div className="row">
<h2>LOREN IPSUM</h2>
<div className="row__posters">
{books.map((book) => (
<img
// onClick={() => handleClick(book)}
key={book.title}
className="row__poster row__posterLarge"
src={book.book_image}
alt={book.title}
/>
))}
</div>
{/* {description && <span /> />} */}
</div>
);
}
setBooks(books);
You are updating the books with their current value.
=> Change this to setBooks(request);
Currently, I am working on a simple React Exercise.
The I am trying to conditionally render a certain part of the jsx based on a certain state.
Basically, my code looks like this
const ShopList = (props) => {
const [isLoading, setIsLoading] = useState(false)
const [isEnd, setIsEnd] = useState(false)
const handleButtonClick = ()=>{
setIsLoading(true)
axios.get('https://codingapple1.github.io/shop/data2.json')
.then((result)=>{
setTimeout(()=>props.addData(result.data),2000)
})
.then(()=>{
setIsEnd(true)
})
.catch((e)=>{
console.log(e)
setIsLoading(false)
})
}
return(
<div className="container">
<div className="row">
{props.shoes.map((shoe,i) => (
<Product shoe={shoe} ind={i+1} key={shoe.id}/>
))}
</div>
{isLoading && <h3>Loading....</h3>}
{!isEnd && <button className="btn btn-light" onClick={handleButtonClick}>More Items</button>}
</div>
)
}
export default ShopList;
The thing is that I am having trouble locating my setIsLoading(false) so that I can hide the <h3>Loading...</h3> after two seconds.
In which part of the handleButtonClick function should I put setIsLoading(false)?
Answering your question, you most likely need to hide "Loading" in both cases:
if the request was successful and not.
So you could do this in the finally section like this:
axios.get(...)
.then(...)
.catch(...)
.finally(() => setIsLoading(false));
I created this React application to practice the fetch API.
However, while writing the code to display the data on the browser via the map method, I got the error message "TypeError: profile.map is not a function". Below is the code:
import React, { Fragment, useEffect, useState } from "react";
import "./App.css";
function App() {
// https://reqres.in/api/users
const [profile, setProfile] = useState([]);
const [loading, setLoading] = useState(false);
const getProfile = async () => {
setLoading(true);
const response = await fetch("https://reqres.in/api/users");
const data = await response.json();
setProfile(data);
setLoading(false);
};
useEffect(() => {
getProfile();
}, []);
return (
<Fragment>
<h1>React fetch</h1>
<div className="main">
<section className="section">
<h2>Get database</h2>
<div>
{loading ? (
<Fragment>loading..</Fragment>
) : (
profile.map(i => {
<Fragment>
<ul>
<li>{i.id}</li>
<li>{i.email}</li>
<li>{i.first_name}</li>
<li>{i.last_name}</li>
<li>
<image src={i.avatar} />
</li>
</ul>
</Fragment>;
})
)}
</div>
</section>
<form className="section">
<h2>Post data</h2>
<input type="text" placeholder="enter detail" />
<button type="submit">Post</button>
</form>
<form className="section">
<h2>Update data</h2>
<select>
<option>Choose data</option>
</select>
<input type="text" placeholder="enter detail" />
<button type="submit">Update</button>
</form>
</div>
</Fragment>
);
}
export default App;
Why isn't map being recognized?
I believe it's because .map is a method for Array prototypes, not for Objects (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map)
You can return the array from the object by using data.data instead of just data:
...
const getProfile = async () => {
setLoading(true);
const response = await fetch("https://reqres.in/api/users");
const data = await response.json();
setProfile(data.data); // probably a safer way to do this, but if you console.log(data) you'll see an object is being returned, not an array.
setLoading(false);
};
...
So, const data = await response.json(); After this line is executed the result we are getting inside the data constant is an Object. We can use MAP function only on Array's, not on Objects. And also the Profile data which you are actually searching is inside the "data" key of the data "constant". So while setting profile data, just use setProfile(data.data);.
A suggestions: Use this Chrome Extension for viewing the API data. It indents the json objects automatically
Map needs to return a value.
{loading ? (
<Fragment>loading..</Fragment>
) : (
profile.map(i => {
return (
<Fragment>
<ul>
<li>{i.id}</li>
<li>{i.email}</li>
<li>{i.first_name}</li>
<li>{i.last_name}</li>
<li>
<image src={i.avatar} />
</li>
</ul>
</Fragment>;
)
})
)}
Also, you cannot use the map function on an object. It looks like your response is an object, what you are looking for is the data from the response. Try this...
setProfile(data.data);
I have dynamic routes based on search results. How do I go back and see my previously rendered search results & search term in input field versus and empty Search page?
I've started looking into useHistory/useLocation hooks, but I'm lost.
1. Search page
export default function Search() {
const [searchValue, setSearchValue] = useState("");
const [isLoading, setIsLoading] = useState(false);
const [noResults, setNoResults] = useState(false);
const [data, setData] = useState([]);
const fetchData = async () => {
const res = await fetch(
`https://api.themoviedb.org/3/search/movie?api_key={API_KEY}&query=${searchValue}`
);
const data = await res.json();
const results = data.results;
if (results.length === 0) setNoResults(true);
setData(results);
setIsLoading(false);
};
function handleSubmit(e) {
e.preventDefault();
setIsLoading(true);
fetchData();
// setSearchValue("");
}
return (
<div className="wrapper">
<form className="form" onSubmit={handleSubmit}>
<input
placeholder="Search by title, character, or genre"
className="input"
value={searchValue}
onChange={(e) => {
setSearchValue(e.target.value);
}}
/>
</form>
<div className="page">
<h1 className="pageTitle">Explore</h1>
{isLoading ? (
<h1>Loading...</h1>
) : (
<div className="results">
{!noResults ? (
data.map((movie) => (
<Result
poster_path={movie.poster_path}
alt={movie.title}
key={movie.id}
id={movie.id}
title={movie.title}
overview={movie.overview}
release_date={movie.release_date}
genre_ids={movie.genre_ids}
/>
))
) : (
<div>
<h1 className="noResults">
No results found for <em>"{searchValue}"</em>
</h1>
<h1>Please try again.</h1>
</div>
)}
</div>
)}
</div>
</div>
);
}
2. Renders Result components
export default function Result(props) {
const { poster_path: poster, alt, id } = props;
return (
<div className="result">
<Link
to={{
pathname: `/results/${id}`,
state: { ...props },
}}
>
<img
src={
poster
? `https://image.tmdb.org/t/p/original/${poster}`
: "https://www.genius100visions.com/wp-content/uploads/2017/09/placeholder-vertical.jpg"
}
alt={alt}
/>
</Link>
</div>
);
}
3. Clicking a result brings you to a dynamic page for that result.
export default function ResultPage(props) {
const [genreNames, setGenreNames] = useState([]);
const {
poster_path: poster,
overview,
title,
alt,
release_date,
genre_ids: genres,
} = props.location.state;
const date = release_date.substr(0, release_date.indexOf("-"));
useEffect(() => {
const fetchGenres = async () => {
const res = await fetch(
"https://api.themoviedb.org/3/genre/movie/list?api_key={API_KEY}"
);
const data = await res.json();
const apiGenres = data.genres;
const filtered = [];
apiGenres.map((res) => {
if (genres.includes(res.id)) {
filtered.push(res.name);
}
return filtered;
});
setGenreNames(filtered);
};
fetchGenres();
}, [genres]);
return (
<div className="resultPage">
<img
className="posterBackground"
src={
poster
? `https://image.tmdb.org/t/p/original/${poster}`
: "https://www.genius100visions.com/wp-content/uploads/2017/09/placeholder-vertical.jpg"
}
alt={alt}
/>
<div className="resultBackground">
<div className="resultInfo">
<h1> {title} </h1>
</div>
</div>
</div>
);
}
4. How do I go back and see my last search results?
I'm not sure how to implement useHistory/useLocation with dynamic routes. The stuff I find online mentions building a button to click and go to last page, but I don't have a button that has to be clicked. What is someone just swipes back on their trackpad?
One way you could do this would be to persist the local component state to localStorage upon updates, and when the component mounts read out from localStorage to populate/repopulate state.
Use an useEffect hook to persist the data and searchValue to localStorage, when either updates.
useEffect(() => {
localStorage.setItem('searchValue', JSON.stringify(searchValue));
}, [searchValue]);
useEffect(() => {
localStorage.setItem('searchData', JSON.stringify(data));
}, [data]);
Use an initializer function to initialize state when mounting.
const initializeSearchValue = () => {
return JSON.parse(localStorage.getItem('searchValue')) ?? '';
};
const initializeSearchData = () => {
return JSON.parse(localStorage.getItem('searchData')) ?? [];
};
const [searchValue, setSearchValue] = useState(initializeSearchValue());
const [data, setData] = useState(initializeSearchData());