I am building an image search app using Unsplash API and trying to implement react infinite scroll (https://www.npmjs.com/package/react-infinite-scroll-component), but it is not working properly.
Now, the search form works fine and it displays 10 images(which is the default number of images using Unsplash API) when you search something, but when I scroll down to the end of of the page, it only displays the loader (h4 'Loading') but it does not display more images.
App.js
import './App.css';
import Main from './components/Main';
function App() {
return (
<div className="App">
<Main />
</div>
);
}
export default App;
Main.js
import React from 'react'
import Header from './Header'
import Image from './Image'
import { useState, useEffect } from 'react'
import InfiniteScroll from 'react-infinite-scroll-component';
function Main() {
const [input, setInput] = useState('')
const [allImages, setAllImages] = useState([])
const [favorites, setFavorites] = useState(() => JSON.parse(localStorage.getItem("favorites")) || [])
useEffect(() => {
localStorage.setItem("favorites", JSON.stringify(favorites))
console.log(favorites)
}, [favorites])
function handleChange(event) {
setInput(event.target.value)
}
async function fetchImages() {
try {
const res = await fetch(`https://api.unsplash.com/search/photos?&query=${input}&client_id=${process.env.REACT_APP_UNSPLASH_API_KEY}`)
const data = await res.json();
setAllImages(data.results)
} catch(error) {
alert("Sum ting wong");
}
}
const handleSubmit = async (event) => {
event.preventDefault();
fetchImages()
}
console.log(`allImages: ${allImages.length}`);
// use parameter 'id' to read specific one
function isLiked(id) {
return favorites.find(el => el.id === id) ? true : false
}
return (
<main>
<Header
input={input}
handleChange={handleChange}
handleSubmit={handleSubmit}
/>
<InfiniteScroll
dataLength={allImages.length} //This is important field to render the next data
next={fetchImages}
hasMore={true}
loader={<h4>Loading...</h4>}
>
<div className='main--image-list mt-5 pb-5'>
{allImages.map(el => (
<Image
key={el.id}
// do need spread operator below for img's src to work in Image.js
{...el}
el={el}
isLiked={isLiked(el.id)}
favorites={favorites}
setFavorites={setFavorites}
/>
))}
</div>
</InfiniteScroll>
</main>
)
}
export default Main
for visuals
Related
I have seen this asked before but I can't seem to be able to wrap my head around it with my situation.
I am using a search bar to filter the data down and it works but the image will not update. The URL passing to the child works fine but it's just not changing its state. I just don't really understand how to implement it.
PokemonList.jsx
import axios from "axios";
import React, { useEffect, useState } from "react";
import PokemonSprite from "./PokemonSprite";
import Card from "#material-tailwind/react/Card";
import CardBody from "#material-tailwind/react/CardBody";
import CardFooter from "#material-tailwind/react/CardFooter";
import H6 from "#material-tailwind/react/Heading6";
import Paragraph from "#material-tailwind/react/Paragraph";
import Button from "#material-tailwind/react/Button";
// const baseURL = "https://pokeapi.co/api/v2/pokemon?limit=898";
const baseURL = "https://pokeapi.co/api/v2/pokemon?limit=20";
export default function PokemonList() {
const [post, setPost] = useState([]);
const [searchTerm, setSearchTerm] = useState('');
useEffect(() => {
axios.get(baseURL).then((response) => {
setPost(response.data.results);
});
}, []);
if (!post) return <p>Sorry, no results.</p>;
return (
<div>
<input type="text" placeholder="Search..." onChange={e => {setSearchTerm(e.target.value)}}/>
{post.filter((data) => {
if (searchTerm == "") {
return data;
} else if (data.name.toLowerCase().includes(searchTerm.toLowerCase())) {
console.log(data);
return data;
}
}).map((data, idx) => (
<div className="p-5">
<Card key={idx}>
<PokemonSprite url={data.url} />
<CardBody>
<H6 color="gray">{data.name}</H6>
<Paragraph color="gray">
Don't be scared of the truth because we need to restart the human
foundation in truth And I love you like Kanye loves Kanye I love
Rick Owens’ bed design but the back is...
</Paragraph>
</CardBody>
<CardFooter>
<Button color="lightBlue" size="lg" ripple="light">
Read More
</Button>
</CardFooter>
</Card>
</div>
))}
</div>
);
}
PokemonSprite.jsx
import axios from "axios";
import React, { useEffect, useState } from "react";
import CardImage from "#material-tailwind/react/CardImage";
export default function PokemonList(url) {
const [post, setPost] = useState();
console.log(url);
useEffect(() => {
axios.get(url.url).then((response) => {
//console.log(response.data);
setPost(response.data);
});
}, []);
if (!post) return <p>Sorry, no results.</p>;
return (
<div>
<CardImage
src={post.sprites.front_default}
alt="Card Image"
/>
</div>
);
}
Please rewrite your PokemonSprite component like this to enable re rendering on updates to the Url...
import axios from "axios";
import React, { useEffect, useState } from "react";
import CardImage from "#material-tailwind/react/CardImage";
export default function PokemonList(url) {
const [post, setPost] = useState();
console.log(url);
const getUpdatedImage = async (imageUrl) => {
const response = await axios.get(imageUrl);
setPost(response.data);
return post;
}
useEffect(() => {
getUpdatedImage(url.url);
}, [url]);
if (!post) return <p>Sorry, no results.</p>;
return (
<div>
<CardImage
src={post.sprites.front_default}
alt="Card Image"
/>
</div>
);
}
So I want to toggle between different categories in my react movie-app such as Trending,Top Rated,Popular etc.I am use useState hook for this,by making the initial state as one category then changing the state through the onClick event on the buttons.But it doesn't seem to be working.What could be the problem?
Code:
App.js
import { useState } from "react";
import Movie from "./components/Movie";
import requests from "./components/ApiRequest";
import Navbar from "./components/Navbar";
function App() {
const [category, setCategory] = useState('top_rated')
return (
<div className="App">
<Navbar setCategory={setCategory} />
<div className="movie-container">
<Movie fetchUrl={"movie/" + category + "?api_key=" + API_KEY + "&language=en-US&page=1"} />
</div>
</div>
);
}
export default App;
Navbar.js
import React from 'react'
import SearchBar from './SearchBar'
import { FiFilter } from 'react-icons/fi'
const Navbar = ({ setCategory }) => {
return (
<div className="navbar-container">
<button className="navbar-btn"><FiFilter />Filter</button>
<div className="categories">
<button className="cat-btn" onClick={() => setCategory("popular")}>Popular</button>
<button className="cat-btn" onClick={() => setCategory("top_rated")}>Top Rated</button>
<button className="cat-btn" onClick={() => setCategory("upcoming")}>Upcoming</button>
</div>
<SearchBar />
</div>
)
}
export default Navbar
Movie.js
const Movie = ({ fetchUrl }) => {
const [movie, setMovie] = useState([]);
useEffect(() => {
async function getPost() {
const response = await client.get(fetchUrl);
console.log(response);
setMovie(response.data.results);
// return response;
}
getPost();
}, [])
return (
movie.map((m) => (
<div className="movie-component" key={m.id}>
<img src={`https://image.tmdb.org/t/p/w500${m.backdrop_path}`} alt="" />
<div className="metadata">
<h1>{m.title}</h1>
<a>⭐{m.vote_average}</a>
</div>
</div>
)
))
}
So I have initialized the useState hook in App.js and then using it in Navbar.js as the set the state of this hook on click event.
useEffect(() => {
async function getPost() {
const response = await client.get(fetchUrl);
console.log(response);
setMovie(response.data.results);
// return response;
}
getPost();
}, [fetchURL])
please update your dependency array as follows.
on changing the category, fetchURL value is being changed.
so it need to be included in dependency array of useEffect Hook.
I tried to make new component for list film. But it seems error in page Home.
Here my code in component list map:
import './Home.css';
import './ListFilm';
import ListFilm from './ListFilm';
function Home() {
return (
<div className="Home">
<h2>The List of Films</h2>
<div className="list film">
<ListFilm/>
</div>
</div>
);
}
export default Home;
Here is component file named ListFilm.js :
import {useState, useEffect} from 'react';
function ListFilm() {
const [post, setPost] = useState({});
useEffect(() => {
const fetchData = async () => {
const response = await fetch(
"https://api.themoviedb.org/3/movie/now_playing?api_key=9e0de5499870264659308848dbad6b2a"
);
const data = await response.json();
setPost(data);
console.log(response)
console.log(data);
};
fetchData();
}, []);
return (
<>
{post.results && post.results.map((item, index) => (
<div key={index}>
<h3>{item.original_title}</h3>
<img src={item.backdrop_path} alt="poster"/>
<p>Tanggal Rilis: {item.release_date}</p>
</div>
))}
</>
);
}
export default ListFilm;
The goal is making data and image in listfilm.js showing in page Home.js. The error code in console log said that index.js:1 The above error occurred in the component: Please help me. Thanks
I' m new to React and I'm building a simple React app that displays all the nations of the world on the screen and a small search bar that shows the data of the searched nation.
Here an image of the site
But I don't know how to show the country you want to click in the scrollbar.
Here the app.js code:
import React, { Component } from 'react';
import './App.css';
import NavBar from '../Components/NavBar';
import SideBar from './SideBar';
import CountryList from '../Components/SideBarComponents/CountryList';
import Scroll from '../Components/SideBarComponents/Scroll';
import Main from './Main';
import SearchCountry from '../Components/MainComponents/SearchCountry';
import SearchedCountry from '../Components/MainComponents/SearchedCountry';
import Datas from '../Components/MainComponents/Datas';
class App extends Component {
constructor() {
super();
this.state = {
nations: [],
searchField: '',
button: false
}
}
onSearchChange = (event) => {
this.setState({searchField: event.target.value});
console.log(this.state.searchField)
}
onClickChange = () => {
this.setState(prevsState => ({
button: true
}))
}
render() {
const {nations, searchField, button, searchMemory} = this.state;
const searchedNation = nations.filter(nation => {
if(button) {
return nation.name.toLowerCase().includes(searchField.toLowerCase())
}
});
return (
<div>
<div>
<NavBar/>
</div>
<Main>
<div className='backgr-img'>
<SearchCountry searchChange={this.onSearchChange} clickChange={this.onClickChange}/>
<SearchedCountry nations={searchedNation}/>
</div>
<Datas nations={searchedNation}/>
</Main>
<SideBar>
<Scroll className='scroll'>
<CountryList nations={nations} clickFunc/>
</Scroll>
</SideBar>
</div>
);
}
componentDidMount() {
fetch('https://restcountries.eu/rest/v2/all')
.then(response => response.json())
.then(x => this.setState({nations: x}));
}
componentDidUpdate() {
this.state.button = false;
}
}
export default App;
The countryList:
import React from 'react';
import Images from './Images';
const CountryList = ({nations, clickFunc}) => {
return (
<div className='container' style={{display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(115px, 3fr))'}}>
{
nations.map((country, i) => {
return (
<Images
key={country.numericCode}
name={country.name}
flag={country.flag}
clickChange={clickFunc}
/>
);
})
}
</div>
)
}
export default CountryList;
And the images.js:
import React from 'react';
import './images.css'
const Images = ({name, capital, region, population, flag, numericCode, clickChange}) => {
return (
<div className='hover bg-navy pa2 ma1 tc w10' onClick={clickChange = () => name}>
<img alt='flag' src={flag} />
<div>
<h6 className='ma0 white'>{name}</h6>
{capital}
{region}
{population}
{numericCode}
</div>
</div>
);
}
export default Images;
I had thought of using the onClick event on the single nation that was going to return the name of the clicked nation. After that I would have entered the name in the searchField and set the button to true in order to run the searchedNation function.
I thank anyone who gives me an answer in advance.
To keep the actual structure, you can try using onClickChange in Images:
onClickChange = (newName = null) => {
if(newName) {
this.setState(prevsState => ({
searchField: newName
}))
}
// old code continues
this.setState(prevsState => ({
button: true
}))
}
then in onClick of Images you call:
onClick={() => {clickChange(name)}}
Or you can try as well use react hooks (but this will require some refactoring) cause you'll need to change a property from a parent component.
With that you can use useState hook to change the value from parent component (from Images to App):
const [searchField, setSearchField] = useState('');
Then you pass setSearchField to images as props and changes the searchField value when Images is clicked:
onClick={() => {
clickChange()
setSearchField(name)
}}
I am using useEffect to hit an api and display some data from the response.It works well in console but when i try to display the data in a component it throws an error.I am checking for the loading state though.I am showing the data after a i get a response then where does this null coming from
App.js file:
import { useState, useEffect } from 'react';
import Details from './components/Details/Details';
import Header from './components/Header/Header';
import GlobalStyle from './globalStyles';
const API_KEY = 'Private';
// const URL = `https://geo.ipify.org/api/v1?apiKey=${API_KEY}&ipAddress=${ip}`;
function App() {
const [ip, setIp] = useState('8.8.8.8');
const [response, setResponse] = useState(null);
const [error, setError] = useState(null);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
const fetchData = async () => {
setIsLoading(true);
try {
const res = await fetch(
`https://geo.ipify.org/api/v1?apiKey=${API_KEY}&ipAddress=${ip}`
);
const json = await res.json();
setResponse(json);
setIsLoading(false);
} catch (error) {
setError(error);
}
};
fetchData();
// return { response, error, isLoading };
}, [ip]);
return (
<>
<GlobalStyle />
<Header getIp={(q) => setIp(q)} />
<Details isLoading={isLoading} res={response} error={error} />
</>
);
}
export default App;
Header.js file:
import { useState } from 'react';
import { FaArrowRight } from 'react-icons/fa';
import React from 'react';
import { Form, FormInput, Head, HeadLine, Button } from './Header.elements';
// import { useFetch } from '../../useFetch';
const Header = ({ getIp }) => {
const [input, setInput] = useState('');
const onChange = (q) => {
setInput(q);
getIp(q);
};
return (
<>
{/* styled components */}
<Head>
<HeadLine>IP Address Tracker</HeadLine>
<Form
onSubmit={(e) => {
e.preventDefault();
onChange(input);
setInput('');
}}
>
<FormInput
value={input}
onChange={(e) => {
setInput(e.target.value);
}}
placeholder='Search for any IP address or Domain'
/>
<Button type='submit'>
<FaArrowRight />
</Button>
</Form>
</Head>
</>
);
};
export default Header;
Details.js file:
import React from 'react';
import { Box, Location } from './Details.elements';
const Details = ({ res, error, isLoading }) => {
console.log(res);
return isLoading ? (
<div>loading...</div>
) : (
<>
<Box>
<Location>{res.location.city}</Location>
</Box>
</>
);
};
export default Details;
the error it shows:
That happens because on the first render, Details component will receive isLoading=false and res=null, so it will try to render the box so it's throwing the error.
You can initialize isLoading as true.
const [isLoading, setIsLoading] = useState(true);
Or render the Location if res has some value.
<Box>
{res && <Location>{res.location.city}</Location>}
</Box>
According to React documentation :
https://reactjs.org/docs/hooks-reference.html
By default, effects run after every completed render, but you can
choose to fire them only when certain values have changed.
So your component is rendering at least once with isLoading as false before even the API call starts.
You have two choices here:
Set isLoading initial value to true
Add optional chaining res?.location.city
https://codesandbox.io/s/stackoverflow-67755606-uuhqk