import React, { useState, useEffect } from 'react'
import axios from 'axios'
function DataApi({ searchTerm }) {
const [users, setUsers] = useState([])
const [loading, setLoading] = useState(false)
const [error, setError] = useState()
useEffect(() => {
axios
.get('https://jsonplaceholder.typicode.com/posts')
.then(res => {
setUsers(res.data);
console.log(res.data);
setLoading(true);
})
.catch(error => {
console.log(error);
setError('Error retrieving data');
});
}, []);
return (
<div>
<div>
{
!loading ?
<h1>...Loading</h1>
:
users.length > 0 && users.filter((item) =>
(searchTerm === '') ? item :
(item.title.toLowerCase().includes(searchTerm.toLocaleLowerCase())) ? item :
// <h1>search result not found</h1>
null
).map((item) => {
return (
<ul key={item.id}>
<li>Name: {item.id}</li>
<li>Title: {item.title}</li>
<li>Body: {item.body}</li>
</ul>
)}
)
}
{
error ? <h1>{error}</h1> : null
}
</div>
</div>
)
}
export default DataApi;
I have made a search field in which user can search the name of the person. If user does not get the searched name then there should be a message come that search result not found. I tried to implement it using if-else (ternary operator) & put the message into else part but it is not working. When I put null instead of search result not found then it works perfectly but I am not able to show the message then. But if I put search result not found instead of null then nothing works, not even filter functionality. Can you guys please help me? Thank you in advancve.
You can simply check the length of user and move the filter method to the useEffect and show a message
import React, { useState, useEffect } from "react";
import axios from "axios";
function DataApi({ searchTerm }) {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState();
const [searchTermTest, setsearchTermTest] = useState();
function handleChange(event) {
setsearchTermTest(event.target.value);
}
useEffect(() => {
axios
.get("https://jsonplaceholder.typicode.com/posts")
.then((res) => {
const data = res.data;
const filteredData = data.filter((dat) =>
dat.title.includes(searchTermTest === undefined ? "" : searchTermTest)
);
setUsers(filteredData);
setLoading(true);
})
.catch((error) => {
console.log("errr", error);
setError("Error retrieving data");
});
}, [searchTermTest]);
return (
<div>
<input type="text" onChange={handleChange} />
<div>
{!loading ? (
<h1>...Loading</h1>
) : (
users.length > 0 &&
users.map((item) => {
return (
<ul key={item.id}>
<li>Name: {item.id}</li>
<li>Title: {item.title}</li>
<li>Body: {item.body}</li>
</ul>
);
})
)}
{users.length === 0 && loading ? <h1>search result not found</h1> : ""}
{error ? <h1>{error}</h1> : null}
</div>
</div>
);
}
export default DataApi;
{users.length === 0 && loading ? <h1>search result not found</h1> : ""}
I have made it in codesandbox
Codesandbox link here
In Array.filter() method you need to return true/false value, that's how it works.
Modified the code and added the renderUser function to take care of user data filter.
DataApi function
function DataApi({ searchTerm }) {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState();
const [searchTermTest, setsearchTermTest] = useState();
function handleChange(event) {
setsearchTermTest(event.target.value);
}
useEffect(() => {
axios
.get("https://jsonplaceholder.typicode.com/posts")
.then((res) => {
const data = res.data;
const filteredData = data.filter((dat) =>
dat.title.includes(searchTermTest === undefined ? "" : searchTermTest)
);
setUsers(filteredData);
setLoading(true);
})
.catch((error) => {
console.log("errr", error);
setError("Error retrieving data");
});
}, [searchTermTest]);
return (
<div>
<input type="text" onChange={handleChange} />
<div>
{!loading ? (
<h1>...Loading</h1>
) : (
users.length > 0 && renderUsers(users, searchTerm) // updated here...
)}
{error ? <h1>{error}</h1> : null}
</div>
</div>
);
}
renderUsers function
const renderUsers = (users, searchTerm) => {
const filteredUsers = users.filter((item) => {
console.log(item.title);
return searchTerm === ""
? true
: item.title.toLowerCase().includes(searchTerm.toLocaleLowerCase())
? true
: false;
});
return filteredUsers.length > 0 ? (
filteredUsers.map((item) => {
return (
<ul key={item.id}>
<li>Name: {item.id}</li>
<li>Title: {item.title}</li>
<li>Body: {item.body}</li>
</ul>
);
})
) : (
<h1>search result not found</h1>
);
};
export default DataApi;
Related
I have an issue with my code, i have a search input and a list of countries
When i type some words i have an error which cause to my app collapse
I've been trying for about two days to find the problem but can't find it.
This is the error message : Uncaught TypeError: Cannot read properties of undefined (reading 'filter')
const Country = ({name, num}) =>{
//console.log(name)
return (
<div>
<p>{name}</p>
</div>
)} // Component
const Input = ({onSearch, search}) =>{
return (
<div>
Find countries: <input onChange={onSearch} value={search} />
</div>
)} // Component
import { useState, useEffect } from "react";
import axios from "axios";
import Input from "./components/Input";
import Country from "./components/Country";
const App = () => {
const [countryList, setCountryList] = useState();
const [search, setSearch] = useState("");
const [filter, setFilter] = useState(false);
useEffect(() => {
axios
.get("https://restcountries.com/v3.1/all")
.then((res) => setCountryList(res.data));
}, []);
const onSearch = (event) => {
if (event.target.value === " ") setFilter(false);
else {
setFilter(true);
setSearch(event.target.value);
}
};
const countriesList = filter
? countryList.filter((country) => {
return country.name.common.includes(search);
})
: null ;
return (
<div>
<Input onSearch={onSearch} search={search} />
{filter ? (
countriesList.length === 0 ? (
<h3>No match</h3>
) : countriesList.length > 10 ? (
<h3>Too many matches, specify another filter...</h3>
) : countriesList.length < 10 && countriesList.length > 1 ? (
countriesList.map((country, i) => (
<Country name={country.name.common} key={i} num={false} />
))
) : (
<Country name={countriesList[0].name.common} num={true} /> &&
console.log("common", countriesList)
)
) : (
<h3>Search for any country</h3>
)}
</div>
);
};
countrylist state must be an array.
Try using array in countyList as its undefined initially
const [countryList, setCountryList] = useState([]);
Also you seems to be you accessing filter (a state value directly while component initilize). Please try replacing with below code. Let me know if issue persists, should be a very simple fix
const [countriesList, setCountriesList] = useState([]);
useEffect(()=>{
if(filter){
setCountriesList(
countryList.filter((country) => {
return country?.name?.common?.includes(search);
}))
}else{
setCountriesList(countryList);
}
},[filter])
I'm working on a project and wanted to try and implement an infinitely scrollable page to list users. Filtering and such works fine and all but every time the scrolling component reaches the ref element the component makes an API call to append to the list and then the parent component re-renders self completely.
const UsersList = () => {
const [searchString, setSearchString] = useState('')
const [next, setNext] = useState('')
const { userList, error, nextPage, loading, hasMore } = useFetch(next)
const [usersList, setUsersList] = useState([])
const observer = useRef()
const lastElemRef = useCallback(
(node) => {
if (loading) return
if (observer.current) observer.current.disconnect()
observer.current = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting && hasMore) {
setNext((prev) => (prev = nextPage))
setUsersList((prev) => new Set([...prev, ...userList]))
}
})
if (node) {
observer.current.observe(node)
}
},
[loading, nextPage, hasMore],
)
useEffect(() => {
setUsersList((prev) => new Set([...prev, ...userList]))
console.log(error)
}, [])
return (
<>
{loading ? (
<CSpinner variant="grow"></CSpinner>
) : (
<CContainer
className="w-100 justify-content-center"
style={{ maxWidth: 'inherit', overflowY: 'auto', height: 600 }}
>
<CFormInput
className="mt-2 "
id="userSearchInput"
value={searchString}
onChange={(e) => {
setSearchString(e.target.value)
}}
/>
{loading ? (
<CSpinner variant="grow"></CSpinner>
) : (
<>
{Array.from(usersList)
.filter((f) => f.username.includes(searchString) || searchString === '')
.map((user) => {
if (Array.from(usersList)[usersList.size - 1] === user) {
return (
<UserCard
key={user.id}
user={user}
parentRef={searchString ? null : lastElemRef}
/>
)
} else {
return <UserCard key={user.id} user={user} />
}
})}
</>
)}
</CContainer>
)}
</>
)
}
export default UsersList
This is the component entirely.
Here's my useFetch hook;
export const useFetch = (next) => {
const [userList, setUserList] = useState([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState('')
const [nextPage, setNextPage] = useState(next)
const [hasMore, setHasMore] = useState(true)
useEffect(() => {
setLoading(true)
setError('')
axios
.get(next !== '' ? `${next}` : 'http://localhost:8000/api/getUsers/', {
headers: {
Authorization: 'Bearer ' + localStorage.getItem('access_token'),
},
})
.then((res) => {
setUserList((userList) => new Set([...userList, ...res.data.results]))
setNextPage((prev) => (prev = res.data.next))
if (res.data.next === null) setHasMore(false)
setLoading(false)
})
.catch((err) => {
setError(err)
})
}, [next])
return { userList, error, nextPage, loading, hasMore }
}
export default useFetch
I'm using Limit Offset Pagination provided by Django Rest Framework, next object just points to the next set of objects to fetch parameters include ?limit and ?offset added at the end of base API url. What is it that I'm doing wrong here ? I've tried many different solutions and nothing seems to work.
Solved
Apparently it was just my back-end not cooperating with my front-end so I've changed up the pagination type and now it seems to behave it self.
I have created a react app which will fetch the api using the values from the url params. which are modified using navigate prop without page refresh.
Here is the code.
const App = () => {
const [itemData, setItemData] = useState({});
const [itemError, setItemError] = useState({});
const [additionalData, setAdditionalData] = useState({});
const [additionalError, setAdditionalError] = useState({});
const [isLoading, setIsLoading] = useState(false);
const [showTrailer, setShowTrailer] = useState(false);
const [trailer, setTrailer] = useState({});
const [trailerError, setTrailerError] = useState({});
const [group, setGroup] = useState([])
const backend_url = process.env.REACT_APP_BACKEND;
const handleCloseTrailer = () => setShowTrailer(false);
const handleShowTrailer = () => setShowTrailer(true);
const location = useLocation();
const id = location.pathname.split("/")[2];
const [searchParams, setSearchParams] = useSearchParams();
const [people, setPeople] = useState([]);
const [groupId, setGroupId] = useState(searchParams.get("group_id"));
const navigate = useNavigate();
function handleChange(value) {
navigate(`?group_id=${value}`);
}
useEffect(() => {
const fetchMainApi = () => {
setIsLoading(true)
axios.get(`${backend_url}/api/v1/metadata?id=${id}`)
.then(function(response) {
if(response.data.content.apiId !== 'undefined') {
axios.get("API_URL")
.then(function (response) {
setAdditionalData(response.data);
})
.catch(function (error) {
setAdditionalError(error);
})
}
if(itemData && (itemData.apiId !== 'null' || 'undefined')) {
axios.get("API_URL")
.then(function(response) {
setTrailer(response.data)
})
.catch(function(error) {
setTrailerError(error)
})
}
if(type === "cat" && itemData.children) {
setGroup(itemData.children)
}
if(type === "cat" && itemData.children)
axios.get("API_URL" + groupId)
.then(function (response) {
setPeople(response.data.content.children);
})
.catch(function (error) {
console.log(error);
});
setItemData(response.data.content)
})
.catch(function(error) {
setItemError(error)
})
setIsLoading(false)
}
fetchMainApi()
}, [backend_url,id,type,itemData.apiId,itemData.api])
return (
<>
<Form.Select onChange={event => handleChange(event.target.value)} aria-label="Default select example">
<option>Group All</option>
{cluster.map((person, index) => (
<option key={guid()} value={group.id}>{group.name}</option>
))}
</Form.Select>
<People people={people}/>
</>
);
};
export default App;
Here is the People component
const People = ({people}) => {
return (
<Row className="m-2 pt-2">
<h2 className="color-white">People</h2>
{people && people.length > 0 && (people.map((people, index) => (
<Col key={index} className="p-lg-4 p-sm-3" xs={12} sm={6} md={4} lg={3} xl={3}>
....
</Col>
)))}
{ (!people || people.length === 0) && (<h5 className="color-white">No Persons Found</h5>) }
</Row>
);
};
export default People;
Working
The select menu updates the query param and then the value of param is taken inside useEffect hook when then provides the data.
Every thing works well but the problem is to update the data inside the component i need to refresh the page when then works as expected.
Is there a way to change or update only the people component without a page refresh.
Hi I'm new to react js and would like to implement infinite scroll without any help of third party/library. I achieved the infinite scroll, but there is problem. The problem is that for example my initial search is ant man movie, and then I try to search new movie let say the hulk. It doesn't re-render, but instead it continue/goes under ant man movies. What I want to achieve is to only shows the movie I search. Hopefully my question and problem is understandable.
Below is my code:
import React, { useEffect, useState, useRef, useCallback } from "react";
import axios from "axios";
const my_key = process.env.REACT_APP_MY_KEY;
export const InfiniteScroll = () => {
const [movies, setMovies] = useState([]);
const [movieName, setMovieName] = useState("ant man");
const [page, setPage] = useState(1);
const [search, setSearch] = useState("");
const [hasMore, setHasMore] = useState(false);
useEffect(() => {
let cancel;
axios({
method: "GET",
url: "http://www.omdbapi.com/",
params: { apikey: `${my_key}`, s: movieName, page: page },
cancelToken: new axios.CancelToken((c) => (cancel = c)),
})
.then(({ data }) => {
if (data.Response === "True") {
setMovies((prev) => [...prev, ...data.Search]);
setHasMore(data.Search.length > 0);
} else {
setMovies((prev) => prev);
}
})
.catch((err) => err);
return () => cancel();
}, [movieName, page]);
const myObserver = useRef();
const myRef = useCallback(
(node) => {
if (myObserver.current) myObserver.current.disconnect();
myObserver.current = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting && hasMore) {
setPage((prev) => prev + 1);
}
});
if (node) myObserver.current.observe(node);
},
[hasMore]
);
const onSubmit = (e) => {
e.preventDefault();
setMovieName(search);
setSearch("");
setPage(1);
};
return (
<>
<h1>Searh Movie</h1>
<form onSubmit={onSubmit}>
<input
type="text"
placeholder="title"
value={search}
onChange={(e) => setSearch(e.target.value)}
/>
<input
type="submit"
value="submit"
disabled={search === "" ? true : false}
/>
</form>
{movies === "False" ? (
<div>
<h1>No Data</h1>
</div>
) : (
movies.map(({ imdbID, Title, Year, Poster }, i) => (
<div key={imdbID + i} ref={myRef}>
<img src={Poster} alt={imdbID} />
<h2>{Title}</h2>
<h3>{Year}</h3>
</div>
))
)}
</>
);
};
After the most bottom of ant man movie, comes the hulk movie. It should be re render and only shows hulk movie.
issue solved by seting setMovies([]) empty after search new movies
First, I would thank you for the support.
As new to the ReactJS world, I am trying to complete a concept example of a product store with some filters as checkbox. The idea is that you select a filter, you get displayed the products that have the selected proprety.
Everything works, except that when you refresh the page you get the filters column and a blank column where products are supposed to appear, even if the console.log(state) give back the correct array of objects.
As you click a checkbox (the filters) it render correctly and the products appear.
The GITHUB LINK for the complete code.
Here the component CardProduct that does not display at refresh.
import React, { useContext, useEffect } from 'react'
import { AppContext } from '../../App'
import { Hearty } from '../Hearty'
import Star from '../Star'
import boiler from '../../images/boiler.png'
import Confronta from '../Confronta'
const CardProduct = ({ count, setCount }) => {
const [state, dispatch] = useContext(AppContext)
console.log('State in CardProduct:', state)
function returnCardProduct () {
return (
state.map((item, i) => {
const { brand, descrizione, prezzo, note, stelle } = item
return (
<div className="row">
<div className="colcard">
<div key={ i } className="card"
style={ { width: 'auto', height: 'auto' } }>
<Hearty/>
<img className="card-img-top" src={ boiler } alt="boiler"/>
<div className="card-body">
<p className="card-title"> { brand.toUpperCase() }</p>
<h6 className="card-text">{ descrizione }</h6>
<Star stelle={ stelle }/>
<h4> { prezzo } </h4>
<h5> { note } </h5>
<Confronta count={ count } setCount={ setCount }/>
</div>
</div>
</div>
</div>
)
}))
}
return (
<div className="container">
{ returnCardProduct() }
</div>
)
}
export default CardProduct
Here the Filters component
import { useContext, useEffect, useState } from 'react'
import { AppContext } from '../App'
const Filters = () => {
const [stock, setStock] = useState([])
const [state,dispatch] = useContext(AppContext)
function fetchInitialStock () {
async function fetchStock () {
let result1 = await fetch('http://localhost:9000/stock').
then(result1 => result1.json()).
then(data => setStock(data))
}
fetchStock()
return stock
}
useEffect (()=>fetchInitialStock(),[])
console.log( 'initStock' ,stock)
return (
<>
<div className="container">
<div className="row">
<div className="categories">
<p>CATEGORIE</p>
<h6>Riscaldamento</h6>
<h6>Casa e acqua</h6>
<h6>Casa</h6>
<h6>Acqua</h6>
</div>
<div className="scegli">
<p>SCEGLI PER</p>
<h6><span><input type="checkbox"
name="DISPONIBILI"
onChange={(e)=> {
e.target.checked ? dispatch({ type: 'DISPONIBILI' }) : dispatch({ type:'PREV' })
} }/>
</span> Disponibili ({stock.map((item) => item.disponibili )}) </h6>
<h6><span><input type="checkbox"
name="PROMO"
onChange={(e)=> e.target.checked ? dispatch({ type: 'PROMO' }) : dispatch({ type: 'PREV' }) }
/> </span>In Promozione ({ stock.map((item) => item.inSconto) }) </h6><br/>
</div>
<div className="marche">
<p>MARCHE</p>
<h6><span><input type="checkbox" name="ariston" onChange={(e)=>{
e.target.checked
? dispatch({ type: 'ARISTON' })
: dispatch({ type: 'PREV' })
}}
/> </span> Ariston ({stock.map((item)=>item.hasOwnProperty('brand')? item.brand.ariston: null)})</h6>
<h6><span><input type="checkbox" name="baxi" onChange={(e)=>{
e.target.checked
? dispatch({ type: 'BAXI' })
: dispatch({ type: 'PREV' })
}}/> </span>Baxi ({stock.map((item)=>item.hasOwnProperty('brand')? item.brand.baxi : null)})</h6><br/>
</div>
<div className="tipologia">
<p>TIPOLOGIA</p>
<h6><span><input type="checkbox" name="condensazione" onChange={(e)=>{
e.target.checked
? dispatch({ type: 'CONDENSAZIONE' })
: dispatch({ type: 'PREV' })
}}/> </span> A Condensazione ({stock.map((item)=>item.hasOwnProperty('tipologia')? item.tipologia.condensazione: null)}) </h6>
<h6><span><input type="checkbox" name="cameraAperta" onChange={(e)=>{
e.target.checked
? dispatch({ type: 'APERTA' })
: dispatch({ type: 'PREV' })
}}/> </span>Camera Aperta ({ stock.map((item)=>item.hasOwnProperty('tipologia')? item.tipologia.cameraAperta: null) }) </h6>
<h6><span><input type="checkbox" name="cameraStagna" onChange={(e)=>{
e.target.checked
? dispatch({ type: 'STAGNA' })
: dispatch({ type: 'PREV' })
}}/> </span>Camera Stagna ({ stock.map((item)=>item.hasOwnProperty('tipologia')? item.tipologia.cameraStagna: null) })</h6><br/>
</div>
</div>
</div>
</>
)
}
export default Filters
..and FINALLY the App()
import CardProduct from './components/CardProduct'
import { createContext, useReducer, useState, useEffect } from 'react'
import Filters from './components/Filters'
import Footer from './components/Footer/Footer'
export const AppContext = createContext()
function App () {
const [count, setCount] = useState(0)
function setInit (data, array) {
data.map((item) => array.push(item))
return array
}
/*Function for setting BOILERS from fetch*/
function fetchInitialBoiler () {
let initB = []
async function fetchBoilers () {
let response = await fetch('http://localhost:9000/boilers').
then(response => response.json()).
then(data => setInit(data, initB))
}
fetchBoilers()
return initB
}
const initBoilers = fetchInitialBoiler()
const [prev, setPrev] = useState([])
const [state, dispatch] = useReducer(reducer, initBoilers)
/* Define the reducer function*/
function reducer (state, action) {
let current
switch (action.type) {
case 'DISPONIBILI':
current = []
current = state.filter((item) => item.disponibile ? item : null)
setPrev(current)
return current
case 'PROMO':
current = []
current = state.filter((item) => item.inSconto ? item : null)
setPrev(current)
return current
case 'ARISTON':
current = []
current = state.filter(
(item) => ((item.hasOwnProperty('brand')) &&
(item.brand === 'Ariston'))
? item
: null)
setPrev(current)
return current
case 'BAXI':
current = []
current = state.filter(
(item) => (item.hasOwnProperty('brand')) && (item.brand === 'Baxi')
? item
: null)
setPrev(current)
return current
case 'CONDENSAZIONE':
current = []
current = state.filter((item) => (item.hasOwnProperty('tipologia')) &&
(item.tipologia === 'condensazione')
? item
: null)
setPrev(current)
return current
case 'APERTA':
current = []
current = state.filter((item) => (item.hasOwnProperty('tipologia')) &&
(item.tipologia === 'camera-aperta')
? item
: null)
setPrev(current)
return current
case 'STAGNA':
current = []
current = state.filter((item) => (item.hasOwnProperty('tipologia')) &&
(item.tipologia === 'camera-stagna')
? item
: null)
setPrev(current)
return current
case 'PREV':
current = []
/*console.log('PREV', prev)*/
return prev
default:
return state
}
}
return (
<>
<AppContext.Provider value={ [state, dispatch] }>
<main>
<div className="container">
<div className="container">
<>
<div className="col align-self-start">
<Filters/>
</div>
<div className="col-9">
<CardProduct count={ count } setCount={ setCount }/>
</div>
</>
</div>
</div>
<>
<div>
<Footer className="footer" count={ count }/>
</div>
</>
</main>
</AppContext.Provider>
</>
)
}
export default App
--- THANK YOU ---
Your initB is an array declartion which doesn't re-render a React component. Replace it with useState to see the updated data after the API call.
const [init, setInit] = useState([]);
Secondly,
const initBoilers = fetchInitialBoiler()
you should invoke the fetchInitialBoiler() after the document is mounted, with the current approach chances are the API is invoked even before the intial mount.
Invoke it in an [useEffect][1] block.
useEffect(()=>{
fetchInitialBoiler() //
// simply invoke it, since you're not returning anything from the function, there's no need to save the response.
}, [])
Your function should set the state in the then/catch block, so that the component tree re-renders in an error case.
async function fetchBoilers () {
let response = await fetch('http://localhost:9000/boilers').
then(response => response.json())
.then(data => setInit(response))
.catch(error => setError(error); // state
}
More on Fetch API: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API
So in the end the problem was to pass the fetch as initialState to useReducer. Searching online I found that is passed throught a dispatch action inside useEffect hook. In this case the useReducer hook is declared with initialState '[]' inside the component. Here the code:
const CardProduct = ({ count, setCount }) => {
const [state, dispatch] = useReducer(reducer, [])
async function initfetch () {
let response = await fetch('http://localhost:9000/boilers').
then(response => response.json()).
then(data => dispatch({
type : 'INITIALIZE',
data: data
}))}
useEffect(() => {
initfetch()
}, [])