I am trying to fetch movie by genres from movie database - reactjs

I am struggling to fetch movies by its genres from API movies database. The error is always say that 'genres.map is not a function' or when I try to call selectedGenre to fetch the movie that associate with each genres by using document.GetElementById.value, it cannot read the .value thing. What I am doing wrong? Thank you ahead
const Discover = () => {
const navigate = useNavigate();
const [movies, setMovies] = useState([]);
const [genres, setGenres] = useState('');
const [searchTerm, setSearchTerm] = useState('');
const currentGenre = document.getElementById('genres').value;
const FEATURED_API = `https://api.themoviedb.org/3/discover/movie?sort_by=popularity.desc&api_key=9a7243213d79e4344f8f16ce3b6098cf`;
const GENRES_API = 'https://api.themoviedb.org/3/genre/movie/list?api_key=9a7243213d79e4344f8f16ce3b6098cf';
const SEARCH_API = 'https://api.themoviedb.org/3/search/movie?api_key=9a7243213d79e4344f8f16ce3b6098cf&query='
useEffect(() => {
getMovies(FEATURED_API),
getGenres(GENRES_API)
}, []);
const getGenres = (API) => {
fetch(API)
.then(res => res.json())
.then((data) => {
setGenres(data.genres);
console.log(data.genres)
})
}
const getMovies = (API) => {
fetch(API)
.then((res) => res.json())
.then((data) => {
setMovies(data.results);
console.log(data.results);
});
};
const handleOnSubmit = (e) => {
e.preventDefault();
if(currentGenre) {
fetch(FEATURED_API + `&with_genres=${currentGenre}`)
.then((res) => res.json())
.then((data) => {
setMovies(data.results)
console.log(data.results)
})
}
setGenres('')
// navigate(`/genre/movie/list/${genres.name}`)
}
const handleOnChange = (e) => {
// if(
// genres.id === movies.genres_ids
// )
setGenres(e.target.value)
}
// if(isFetching) return <Loader type='Loading films...'/>;
// if (error) return <Error />;
return (
<div className='flex flex-col'>.
<div className='w-full flex justify-between items-center sm:flex-row flex-col mt-4 mb-10'>.
<form onSubmit={handleOnSubmit}\>
<h2 className='font-bold text-3xl text-black text-left ml-4'>Discover</h2>
<select
value={currentGenre}
onChange={handleOnChange}
className='mt-4 ml-3'id='genres'\>
{genres.length > 0 &&genres.map((genre, i) => <option key={i} value={genres}>{genre.name}</option>)}
</select>
</form>
</div>
<div className='flex flex-wrap sm:justify-start justify-center gap-8'>
{movies.length > 0 &&
movies.map((movie) => <FilmCard key={movie.id} {...movie} />)}
</div>
</div>
)}
export default Discover

I would start by structuring the data fetch differently, something more compact..
Like:
export const useData = (url) => {
const [state, setState] = useState();
useEffect(() => {
const dataFetch = async () => {
const data = await (await fetch(url)).json();
setState(data);
};
dataFetch();
}, [url]);
return { data: state };
};
Maybe error handling too, like:
function App() {
const [state, setState] = useState([])
const [hasError, setHasError] = useState(false)
const {loading, setLoading} = useState(false)
useEffect(() => {
setLoading(true)
fetch("/api/data").then(
res => {
setState(res.data);
setLoading(false)}
).catch(err => {
setHasError(true))
setLoading(false)})
}, [])
return (
<>
{
loading ? <div>Loading...</div> : hasError ? <div>Error occured.</div> : (state.map( d => <div>{d}</div>))
}
</>
)
}
I hope it helps you ,
Cheers!

Related

React Context loosing state on page refresh

So basicaly i have a subreddit context where i get a bunch of subreddis from the api
import axios from "axios";
import { createContext, useCallback, useEffect, useState } from "react";
import { getUniqueObjects } from "../Helpers/Helpers";
import { ChildrenType } from "../Types/ProviderChildrenType";
import { Subreddit, SubredditsResponse } from "../Types/Subreddits";
type InitialState = {
subredditsData?: Subreddit[];
subredditsLoading?: boolean;
subredditsError?: boolean;
subredditsHasMore?: boolean;
getSubreddits?: (arg0: string) => void;
};
export const SubredditContext = createContext<InitialState>({});
export const SubredditContextProvider = ({ children }: ChildrenType) => {
const [data, setData] = useState<Subreddit[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(false);
const [hasMore, setHasMore] = useState(true);
const UseSubreddits = (url: string) => {
const apiCall = useCallback(() => {
setLoading(true);
axios
.get(url)
.then((response: SubredditsResponse) => {
setData(getUniqueObjects([...data, ...response.data]));
setHasMore(response.data.length > 0);
setLoading(false);
})
.catch((err) => {
setError(err);
});
}, [url]);
useEffect(() => apiCall(), [apiCall]);
};
return (
<SubredditContext.Provider
value={{
subredditsData: data,
subredditsLoading: loading,
subredditsError: error,
subredditsHasMore: hasMore,
getSubreddits: UseSubreddits,
}}
>
{children}
</SubredditContext.Provider>
);
};
In my home page I trigger the custom hook of the context "UseSubreddits" which I pass it as the "getSubreddits" prop,
function Homepage() {
const navigate = useNavigate();
const [pageNumber, setPageNumber] = useState(1);
const {
subredditsData,
subredditsLoading,
subredditsError,
subredditsHasMore,
getSubreddits,
} = useContext(SubredditContext);
getSubreddits!(`https://6040c786f34cf600173c8cb7.mockapi.io/subreddits?page=${pageNumber}&limit=16`)
window.addEventListener("scroll", () => {
if (
window.scrollY + window.innerHeight >=
document.documentElement.scrollHeight &&
subredditsHasMore
) {
setPageNumber(pageNumber + 1);
}
});
return (
<>
<Navbar pageTitle="subreddits" />
<div className="homepage">
<div className="homepage__subreddits">
{subredditsData?.map((item) => {
return (
<div key={item.id}>
<SubredditCard
key={item.id}
onClick={() => navigate(`/posts/${item.id}`)}
title={item.title}
description={item.description}
/>
</div>
);
})}
</div>
</div>
<div className="homepage__text">
{subredditsLoading && <h2>Loading...</h2>}
{subredditsError && (
<h2>An error has occured please refresh your page.</h2>
)}
</div>
</>
);
}
export default Homepage;
I have the same kind of context file where I get the posts of the selected subreddit
type InitialState = {
postData?: Post[];
postLoading?: boolean;
postError?: boolean;
getPost?: (arg0: string) => void;
voteHandler?: (
arg0: string,
arg1: string,
arg2: boolean,
arg3: boolean
) => void;
};
export const PostContext = createContext<InitialState>({});
export const PostContextProvider = ({ children }: ChildrenType) => {
const [data, setData] = useState<Post[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(false);
const UsePosts = (url: string) => {
const apiCall = useCallback(() => {
setLoading(true);
axios
.get(url)
.then((response) => {
setData(response.data);
setLoading(false);
})
.catch((err) => {
setError(err);
});
}, [url]);
useEffect(() => {
apiCall();
}, [apiCall]);
};
return (
<PostContext.Provider
value={{
postData: data,
postLoading: loading,
postError: error,
getPost: UsePosts,
}}
>
{children}
</PostContext.Provider>
);
};
and then I do the same thing in the Post component as I do in the Homepage
function Posts() {
const { subredditId } = useParams();
const [urlParam, setUrlParam] = useState("");
const {
postData,
postLoading,
postError,
getPost,
voteHandler,
} = useContext(PostContext);
const { subredditsData } = useContext(SubredditContext);
const selectedSubreddit = useMemo(
() => subredditsData!.find((subreddit) => subreddit.id === subredditId),
[subredditsData]
);
const navigate = useNavigate();
const sortByTitle = "?sortBy=title";
getPost!(
`https://6040c786f34cf600173c8cb7.mockapi.io/subreddits/${subredditId}/posts${urlParam}`
);
return (
<>
<Navbar pageTitle={selectedSubreddit!.title} />
<div className="posts-screen">
<div className="posts-screen__left-panel">
<div className="posts-screen__left-panel-content">
<SortBy onClick={() => setUrlParam(sortByTitle)} />
<div className="posts-screen__posts-container">
{postData?.map((post) => {
return (
<PostCard
key={post.id}
id={post.id}
title={post.title}
description={post.body}
user={post.user}
voteCount={post.upvotes - post.downvotes} ...
Everything works fine except that in the post screen if I reload the page the subreddit state is lost and the posts screen gives and error. I know that the state is lost on refresh but shouldn't it make the api call again for the subreddits?
I'm new at Context so I don't know how to handle it better

Live pagination on search bar with hooks in reactjs

Hello guys i am trying to implement the live pagination of searched items. I have done only the pagination for all the products but not for the searching ones and i am having some problems into writting it.
Thank you for your time.
This is app.js:
const [loading, setLoading] = useState(true);
const [data, setData] = useState([]);
const [currentData, setCurrentData] = useState([]);
const [columns, setColumns] = useState([]);
const [currentPage, setCurrentPage] = useState(1);
const [dataPerPage, setDataPerPage] = useState(10);
const [searchTerm, setSearchTerm] = useState("");
const [filteredProduct, setFilteredProducts] = useState("");
useEffect(() => {
var myHeaders = new Headers();
myHeaders.append("Accept", "text/plain");
myHeaders.append(
"Authorization",
"Bearer xxx"
);
var requestOptions = {
method: "GET",
headers: myHeaders,
redirect: "follow",
};
fetch("http://localhost:5000/api/WarehousStock", requestOptions)
.then((response) => response.json())
.then((result) => {
setData(result);
})
.catch((error) => console.log("error", error))
.finally(() => setLoading(false));
}, []);
// this will run evertime one of the following state will change => data, currentPage, dataPerPage
useEffect(() => {
// generate dynamically columns from first object from array
setColumns(
Object.keys(data[0] || []).map((key) => ({
Header: key,
accessor: key,
}))
);
filterData();
}, [data, currentPage, dataPerPage]);
var paginate = (pageNumber) => setCurrentPage(pageNumber);
function filterData() {
const indexOfLastData = currentPage * dataPerPage;
const indexOfFirstData = indexOfLastData - dataPerPage;
// if there is a search term
if (searchTerm !== '') {
let result =
data.filter(data => {
return data.articleName.toLowerCase().includes(searchTerm.toLowerCase())
})
result = result.slice(indexOfFirstData, indexOfLastData)
setCurrentData([...result])
} else {
// if there is no a search term
setCurrentData(data.slice(indexOfFirstData, indexOfLastData));
}
}
useEffect(() => {
filterData();
}, [searchTerm])
if (loading) return <p>Loading...</p>;
return (
<Styles>
<div className="SearchButton"><input type="text" placeholder="Search name of product" onChange={event => { setSearchTerm(event.target.value) }} /></div>
<Table columns={columns} data={currentData} />
<Pagination
dataPerPage={dataPerPage}
totalData={data.length}
paginate={paginate}
/>
</Styles>
);
}
This is the pagination component:
const Pagination = ({ dataPerPage, totalData, paginate }) => {
const pageNumbers = [];
for (let i = 1; i <= Math.ceil(totalData / dataPerPage); i++) {
pageNumbers.push(i);
}
return (
<nav>
<ul className="pagination mt-4">
{pageNumbers.map((number) => (
<li key={number} className="page-item">
<a onClick={() => paginate(number)} href="!#" className="page-link">
{number}
</a>
</li>
))}
</ul>
</nav>
);
};
Thank you for your help!

In reactjs i want to render like 'no data found' when api request return empty array

const [items, setItems] = useState([]);
useEffect(() => {
axios
.get(
`https://intense-wave-00513.herokuapp.com/addedProducts/${user.displayName}`
)
.then((data) => {
setItems(data.data);
});
}, [user.displayName]);
return(
<div>
{
items?.length?<p>somthing</p>:<Spinner/>
}
</div>
)
But when the api return empty data how to show 'no data found'
In my case
spinner is running all the time when no data found
const [items, setItems] = useState([]);
const [isLoading, setLoading] = useState(false);
useEffect(() => {
setLoading(true);
axios
.get(
`https://intense-wave-00513.herokuapp.com/addedProducts/${user.displayName}`
)
.then((data) => {
setItems(data.data);
setLoading(false);
});
}, [user.displayName]);
return(
<div>
{
!isLoading? <> {items.length ? <p>somthing</p> : <p>data not found</p>} </> : <Spinner/>
}
</div>
)

stop/prevent scrolling to top on re render in React using Hooks (need to implement infinite scrolling)

I am trying to setup infinite scrolling using React Hooks, I am getting the data correctly from the node backend (3 dataset per request), and when I scroll new data is also added correctly in the array (in the loadedPlaces state), but the page is going back to top on re render, I need to prevent this behavior. How do I prevent this, and below is my code
import React, { useEffect, useState } from "react";
import "./App.css";
function App() {
const [page, setPage] = useState(1);
const [loadedPlaces, setLoadedPlaces] = useState([]);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
const getPlaces = async () => {
try {
setIsLoading(true);
const url = `http://localhost:5000/api/places/user/${id}?page=${page}&size=3`;
const responseData = await fetch(url);
const data = await responseData.json();
console.log(data);
setLoadedPlaces((prev) => [...prev, ...data.places]);
setIsLoading(false);
} catch (error) {}
};
getPlaces();
}, [page]);
window.onscroll = function (e) {
if (
window.innerHeight + document.documentElement.scrollTop ===
document.documentElement.offsetHeight
) {
setPage(page + 1);
}
};
return (
<div className='App'>
<h1>Infinite Scroll</h1>
{!isLoading &&
loadedPlaces.length > 0 &&
loadedPlaces.map((place, index) => (
<div key={index} className={"container"}>
<h1>{place.location.lat}</h1>
<h1>{place.location.lng}</h1>
<h1>{place.title}</h1>
<h1>{place.description}</h1>
<h1>{place.address}</h1>
<hr></hr>
</div>
))}
</div>
);
}
export default App;
Any help is highly appreciated
This is happening because whenever you scroll you are calling
window.onscroll = function (e) {
if (
window.innerHeight + document.documentElement.scrollTop ===
document.documentElement.offsetHeight
) {
setPage(page + 1);
}
};
And it's changing the page count and that changed page count leads to again run the
useEffect(() => {
const getPlaces = async () => {
try {
setIsLoading(true);
const url = `http://localhost:5000/api/places/user/${id}?page=${page}&size=3`;
const responseData = await fetch(url);
const data = await responseData.json();
console.log(data);
setLoadedPlaces((prev) => [...prev, ...data.places]);
setIsLoading(false);
} catch (error) {}
};
getPlaces();
}, [page]);
and in that function, you are doing setIsLoading(true) so that it is again rendering this because of
{!isLoading && <-----
loadedPlaces.length > 0 &&
loadedPlaces.map((place, index) => (
<div key={index} className={"container"}>
<h1>{place.location.lat}</h1>
<h1>{place.location.lng}</h1>
<h1>{place.title}</h1>
<h1>{place.description}</h1>
<h1>{place.address}</h1>
<hr></hr>
</div>
))}
And that leads you to the top of the page
You can try this approach:
import React, { useEffect, useState } from "react";
import "./App.css";
function App() {
const [page, setPage] = useState(1);
const [loadedPlaces, setLoadedPlaces] = useState([]);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
const getPlaces = async () => {
try {
setIsLoading(true);
const url = `http://localhost:5000/api/places/user/${id}?page=${page}&size=3`;
const responseData = await fetch(url);
const data = await responseData.json();
console.log(data);
setLoadedPlaces((prev) => [...prev, ...data.places]);
setIsLoading(false);
} catch (error) {}
};
getPlaces();
}, [page]);
window.onscroll = function (e) {
if (
window.innerHeight + document.documentElement.scrollTop ===
document.documentElement.offsetHeight
) {
setPage(page + 1);
}
};
return (
<div className='App'>
<h1>Infinite Scroll</h1>
{
loadedPlaces.length > 0 &&
loadedPlaces.map((place, index) => (
<div key={index} className={"container"}>
<h1>{place.location.lat}</h1>
<h1>{place.location.lng}</h1>
<h1>{place.title}</h1>
<h1>{place.description}</h1>
<h1>{place.address}</h1>
<hr></hr>
</div>
))}
</div>
);
}
export default App;
You can add this.
function ScrollToBottom(){
const elementRef = useRef();
useEffect(() => elementRef.current.scrollIntoView());
return <div ref={elementRef} />;
};
And then:
return (
<div className='App'>
<h1>Infinite Scroll</h1>
{!isLoading &&
loadedPlaces.length > 0 &&
loadedPlaces.map((place, index) => (
<div key={index} className={"container"}>
<h1>{place.location.lat}</h1>
<h1>{place.location.lng}</h1>
<h1>{place.title}</h1>
<h1>{place.description}</h1>
<h1>{place.address}</h1>
<hr></hr>
</div>
))}
<ScrollToBottom />
</div>
);

Too many re-renders for component

I am trying to call a component that shows the details of a notification when the notification is clicked. However, I kept on getting an error of too many re-renders.
This is my Notifications code
This component calls the database to get the list of notifications and then sets the first notification as the default notification clicked.
const Notification = (hospital) => {
const [users, setUsers] = useState([]);
const [search, setSearch] = useState(null);
const [status, setStatus] = useState(null);
const [notifDetails, setNotification] = useState();
useEffect(async () => {
await axios
.get("/notifications")
.then((res) => {
const result = res.data;
setUsers(result);
setNotification(result[0]);
})
.catch((err) => {
console.error(err);
});
}, []);
return(
<div className="hospital-notif-container">
{filteredList(users, status, search).map((details, index) => {
for (var i = 0; i < details.receiver.length; i++) {
if (
(details.receiver[i].id === hospital.PK ||
details.receiver[i].id === "others") &&
details.sender.id !== hospital.PK
) {
return (
<div
className="hospital-notif-row"
key={index}
onClick={() => setNotification(details)}
>
<div className="hospital-notif-row">
{details.name}
</div>
</div>
);
}
}
return null;
})}
</div>
<NotificationDetails details={notifDetails} />
);
}
For NotificationDetails:
This function is triggered when a notification is clicked from Notifications. The error is said to be coming from this component.
const NotificationDetails = ({ details }) => {
const [loading, setLoading] = useState(true);
useEffect(() => {
if (Object.keys(details).length != 0) {
setLoading(false);
}
}, [details]);
if (!loading) {
return (
<>
<div className="hospital-details-container">
<h2>{details.sender.name}</h2>
</div>
</>
);
} else {return (<div>Loading</div>);}
};
What should I do to limit the re-render? Should I change the second argument of the useEffects call? Or am I missing something in my component?
I tried calling console.log from NotificationDetails and it shows that it is infinitely rendering with the data I set in axios which is result[0]. How is this happening?
Your problem should be in NotificationDetails rendering. You should write something like:
const NotificationDetails = ({ details }) => {
const [loading, setLoading] = useState(true);
useEffect(() => {
if (details.length != 0) {
setLoading(false);
}
}, [details]);
return (
<div>
{loading &&
<div className="hospital-details-container">
<div className="hospital-details-header">
<h2>{details.sender.name}</h2>
</div>
</div>
}
{!loading &&
<div>
<ReactBootStrap.Spinner animation="border" />
</div>
}
</div>
);
}
With return outside the condition statement.
EDIT
Now I noted that you have an async useEffect that is an antipattern. You should modify your useEffect in this way:
useEffect(() => {
(async () => {
await axios
.get("/notifications")
.then((res) => {
const result = res.data;
setUsers(result);
setNotification(result[0]);
})
.catch((err) => {
console.error(err);
});
})()
}, []);

Resources