So I was trying to build a server side pagination that works like a static one and I'm almost there, But I've encountered some issues which I cannot seem to solve.
This is what my code looks like
const LiveIndex = (props) => {
const [currentPage, setCurrentPage] = useState(0);
const [isLoading, setLoading] = useState(false);
const startLoading = () => setLoading(true);
const stopLoading = () => setLoading(false);
useEffect(() => {
//After the component is mounted set router event handlers
Router.events.on("routeChangeStart", startLoading);
Router.events.on("routeChangeComplete", stopLoading);
return () => {
Router.events.off("routeChangeStart", startLoading);
Router.events.off("routeChangeComplete", stopLoading);
};
}, []);
const paginationHandler = (page) => {
const currentPath = props.router.pathname;
const currentQuery = props.router.query;
currentQuery.page = currentPage + 1;
props.router.push({
pathname: currentPath,
query: currentQuery,
});
setCurrentPage(currentQuery.page);
};
const backToLastPage = (page) => {
const currentPath = props.router.pathname;
const currentQuery = props.router.query;
currentQuery.page = currentPage - 1;
setCurrentPage(currentQuery.page); // THE code that breaks my code.
props.router.push({
pathname: currentPathh,
query: currentQueryy,
});
};
let content;
if (isLoading) {
content = (
<div>
<h2 class="loading-text">loading.</h2>
</div>
);
} else {
//Generating posts list
content = (
<div className="container">
<h2> Live Games - </h2>
<div className="columns is-multiline">
<p>{props.games.name}</p>
</div>
</div>
);
}
return (
<>
<div className={"container-md"}>
<div>{content}</div>
{props.games.length ? (
<a onClick={() => paginationHandler(currentPage)}> moore </a>
) : (
backToLastPage(currentPage)
)}
</div>
</>
);
};
export async function getServerSideProps({ query }) {
const page = query.page || 1; //if page empty we request the first page
const response = await fetch(
`exampleapi.com?sort=&page=${page}&per_page=10&token`
);
const data = await response.json();
return {
props: {
games: data,
},
};
}
export default withRouter(LiveIndex);
The issue is my backToLastPage does the job well but I'm unable to use setCurrentPage() in that function, Every time I use that I get the following error
Uncaught Invariant Violation: Too many re-renders. React limits the number of renders to prevent an infinite loop
How can I possibly update the value of my currentPage state in the backToLast function
Thank you
You're calling backToLastPage directly in JSX which will be re-rendered/re-called every time. And setCurrentPage (with useState) triggers re-rendering for state changes in backToLastPage.
You can imagine that every time the state changes, your component gets rendered and it will set states again that make infinite renderings for the component.
You can use useEffect to handle props.games changes. That will help you to trigger backToLastPage only once whenever props.games get changed.
React.useEffect(() => {
if(!props.games || !props.games.length) {
backToLastPage(currentPage)
}
},[props.games])
Full modification can be
const LiveIndex = (props) => {
const [currentPage, setCurrentPage] = useState(0);
const [isLoading, setLoading] = useState(false);
const startLoading = () => setLoading(true);
const stopLoading = () => setLoading(false);
useEffect(() => {
//After the component is mounted set router event handlers
Router.events.on("routeChangeStart", startLoading);
Router.events.on("routeChangeComplete", stopLoading);
return () => {
Router.events.off("routeChangeStart", startLoading);
Router.events.off("routeChangeComplete", stopLoading);
};
}, []);
//The main change is here
//It will be triggered whenever `props.games` gets updated
React.useEffect(() => {
if(!props.games || !props.games.length) {
backToLastPage(currentPage)
}
},[props.games])
const paginationHandler = (page) => {
const currentPath = props.router.pathname;
const currentQuery = props.router.query;
currentQuery.page = currentPage + 1;
props.router.push({
pathname: currentPath,
query: currentQuery,
});
setCurrentPage(currentQuery.page);
};
const backToLastPage = (page) => {
const currentPath = props.router.pathname;
const currentQuery = props.router.query;
currentQuery.page = currentPage - 1;
setCurrentPage(currentQuery.page); // THE code that breaks my code.
props.router.push({
pathname: currentPathh,
query: currentQueryy,
});
};
let content;
if (isLoading) {
content = (
<div>
<h2 class="loading-text">loading.</h2>
</div>
);
} else {
//Generating posts list
content = (
<div className="container">
<h2> Live Games - </h2>
<div className="columns is-multiline">
<p>{props.games.name}</p>
</div>
</div>
);
}
return (
<>
<div className={"container-md"}>
<div>{content}</div>
{props.games.length && (
<a onClick={() => paginationHandler(currentPage)}> moore </a>
)}
</div>
</>
);
};
export async function getServerSideProps({ query }) {
const page = query.page || 1; //if page empty we request the first page
const response = await fetch(
`exampleapi.com?sort=&page=${page}&per_page=10&token`
);
const data = await response.json();
return {
props: {
games: data,
},
};
}
export default withRouter(LiveIndex);
Related
I have a hook that rules my requests. It has state "loading" that becoming true while loading
export const useHttp = () => {
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const request = useCallback(async (url, method = 'GET', body = null, headers = {'Content-Type': 'application/json' }) => {
setLoading(true);
try {
const res = await fetch(url, {method, body, headers});
if (!res.ok) {
throw new Error(`Could not fetch ${url}, status: ${res.status}`);
}
const data = await res.json();
setLoading(false);
return data;
} catch(e) {
setLoading(false);
setError(e.message);
throw e;
}
}, [])
const clearError = useCallback(() => {
setError(null);
}, [])
return {
loading,
error,
request,
clearError
}
}
Also i have a service that makes requests:
import { useHttp } from "../hooks/http.hook";
const useNasaService = () => {
const { loading, error, request, clearError } = useHttp();
const _apiBase = 'https://api.nasa.gov/';
const _apiKey = 'api_key=DEMO_KEY';
const getMissionManifest = async (rover) => {
const res = await request(`${_apiBase}mars-photos/api/v1/manifests/${rover}/?${_apiKey}`);
return _transformManifestData(res.photo_manifest);
}
const getImagesData = async (rover, sol, page = 1) => {
const res = await request(`${_apiBase}mars-photos/api/v1/rovers/${rover}/photos?sol=${sol}&page=${page}&${_apiKey}`);
return res.photos.map(_transformImagesData);
}
const _transformImagesData = (data) => {
return {
id: data.id,
sol: data.sol,
earthDate: data.earth_date,
path: data.img_src,
camera: data.camera.full_name,
rover: data.rover.name
}
}
const _transformManifestData = (data) => {
return {
landingDate: data.landing_date,
launchDate: data.launch_date,
maxDate: data.max_date,
maxSol: data.max_sol,
name: data.name,
photos: data.photos,
status: data.status,
totalPhotos: data.total_photos
}
}
return {
loading,
error,
clearError,
getImagesData,
getMissionManifest
}
}
export default useNasaService;
Finally i have a component that needs state "loading" for disabling the inputs.
The question is why "loading" is never getting true in this component:
import useNasaService from '../../services/useNasaService';
const RoverFilter = (props) => {
const { loading } = useNasaService();
console.log(loading); /* always false */
const onRadioChange = (e) => {
props.onRoverSelected(e.target.value);
props.onRoverClicked(e.target.value);
}
return (
<div className="roverFilter" >
<h2 className="roverFilter__title">Select rover</h2>
<div className="roverFilter__inputs">
<label htmlFor="curiosity">Curiosity</label>
<input disabled={loading} type="radio" name="rover-choise" id="curiosity" value="curiosity" onChange={onRadioChange}/>
<label htmlFor="opportunity">Opportunity</label>
<input disabled={loading} type="radio" name="rover-choise" id="opportunity" value="opportunity" onChange={onRadioChange}/>
<label htmlFor="spirit">Spirit</label>
<input disabled={loading} type="radio" name="rover-choise" id="spirit" value="spirit" onChange={onRadioChange}/>
<label htmlFor="perseverance">Perseverance</label>
<input disabled={loading} type="radio" name="rover-choise" id="perseverance" value="perseverance" onChange={onRadioChange}/>
</div>
</div>
)
}
export default RoverFilter;
By the way, in my app there are another components, where "loading" becoming true without any problems. I cant see the difference.
for example, here loading works good:
import { useEffect, useState } from 'react';
import { CSSTransition, TransitionGroup } from 'react-transition-group';
import useNasaService from '../../services/useNasaService';
import ImageGallerySkeleton from '../imageGallerySkeleton/ImageGallerySkeleton';
import Spinner from '../spinner/Spinner';
import ErrorMessage from '../errorMessage/ErrorMessage';
import SliderModal from '../sliderModal/SliderModal';
const ImageGallery = (props) => {
const {loading, getImagesData, clearError, error} = useNasaService();
const [imagesData, setImagesData] = useState([]);
const [nextPage, setNextPage] = useState(1);
const [firstLoading, setFirstLoading] = useState(true);
const [imagesDataLoaded, setImagesDataLoaded] = useState(false);
const [itemIndex, setItemIndex] = useState(0);
const [sliderOpen, setSliderOpen] = useState(false);
const transitionDuration = 1000;
const onImagesDataLoaded = (newData) => {
setImagesData(data => [...data, ...newData]);
setNextPage(page => page + 1);
setFirstLoading(false);
setImagesDataLoaded(true);
}
const onRequestImages = (rover, sol, page) => {
clearError();
if (!rover || !sol) return;
getImagesData(rover, sol, page)
.then(onImagesDataLoaded);
}
const onSliderClosed = () => {
setSliderOpen(false);
}
useEffect(() => {
onRequestImages(props.selectedRover, props.selectedSol, nextPage);
// eslint-disable-next-line
}, [props.selectedRover, props.selectedSol])
if (sliderOpen) {
document.body.style.overflow = "hidden";
} else {
document.body.style.overflow = "visible";
}
function renderItemList(arr) {
const itemList = arr.map((item, i) => {
return (
<CSSTransition
key={item.id}
in={imagesDataLoaded}
timeout={transitionDuration}
classNames='imageGallery__card'>
<li className="imageGallery__card"
onClick={() => {
setSliderOpen(true);
setItemIndex(i);
}}>
<img src={item.path} alt="img from mars"/>
<div className="imageGallery__descr">
<ul>
<li>Rover: {item.rover}</li>
<li>Earth_date: {item.earthDate}</li>
<li>Sol: {item.sol}</li>
<li>{item.camera}</li>
</ul>
</div>
</li>
</CSSTransition>
)
})
return (
<ul className="imageGallery__list">
<TransitionGroup component={null}>
{itemList}
</TransitionGroup>
</ul>
)
}
const spinner = loading && firstLoading ? <Spinner/> : null;
const skeleton = imagesData.length === 0 && firstLoading && !loading && !error ? <ImageGallerySkeleton/> : null;
const items = renderItemList(imagesData);
const errorMessage = error ? <ErrorMessage/> : null;
const counter = imagesData.length === 0 || error ? null :
<h2 className="imageGallery__title">
Showed {loading ? "..." : imagesData.length} photos of {props.totalPhotosInSol}
</h2>
const button = props.totalPhotosInSol === imagesData.length ? null :
<button
onClick={() => onRequestImages(props.selectedRover, props.selectedSol, nextPage)}
disabled={loading}
className="imageGallery__btn">{loading ? "Loading..." : "Load next page" }
</button>
const slider = <SliderModal
open={sliderOpen}
items={imagesData}
slideIndex={itemIndex}
onSliderClosed={onSliderClosed} />
const wrapStyles = firstLoading && loading ? {"padding": "50px"} : null;
return (
<section className="imageGallery" style={wrapStyles}>
{counter}
{spinner}
{skeleton}
{imagesData.length === 0 && !firstLoading ?
<h2 className="imageGallery__title">There is no photo for this sol</h2> :
items
}
{button}
{errorMessage}
{slider}
</section>
)
}
export default ImageGallery;
When you call useState in two different components, those states are independant from eachother. This is still true if you move the useState calls inside a custom hook. If two components call useNasaService (which calls useHttp, which calls useState), then the two components are creating their own states and own functions. If component A starts loading data, that will have no effect on component B.
So ImageGallery is working because it makes a call to getImagesData. This sets the loading state of ImageGallery to true. No other components are affected by this though. When the loading finishes, ImageGallery will set state to have the new data, but again, no other components can use this. RoverFilter on the other hand never calls getImagesData, so its loading state stays false, and it never gets any data.
In react, the typical way to share data is to lift state up. You have a component higher up in the tree, which is responsible for loading the data and setting state. That component then passes the data and functions down to any children that need it. You can either pass the data down using props, or if you need to pass the data a long distance you can consider using context instead.
There's also a wide variety of 3rd party libraries which can be used to manage global state. For example, Redux, Jotai, Zustand, MobX. These can make it simpler to share data between components in far-flung parts of the component tree.
Im new to react and im trying to auto scroll to Section where data will be shown after being fetched
scrolling should be after data is fetched
const App = () => {
const [Data ,setData] = useState([])
const [InputTracking , setInputTracking] = useState('')
const [isLoading,setIsLoading] = useState(false)
const [Clicked , setClicked] = useState(false)
//button onClick function
const searchTrackingID = async (TrackingID) => {
console.log(TrackingID)
setIsLoading(true);
const response = await axios(API_URL+TrackingID) ;
const res = await response.data ;
setData(res)
setIsLoading(false);
setClicked(true);
}
}
return (
<Header />
//here a form with a text input and button (after clicking it , wait for Data to change state then AUTO SCROLL TO Data (section)
<section >
<div >
<div >
{
Data.slice(1).reverse().map((mydata)=>(
<TrackingInfo Maindata={mydata}/>
))
}
</div>
</div>
</section>
)
function TestComponent() {
const testRef = useRef(null);
const scrollToElement = () => testRef.current.scrollIntoView();
useEffect(()=>{
if(loading){
scrollToElement()
}
}, [loading])
// Once the scrollToElement function is run, the scroll will show the element
return (
<>
<div ref={testRef}>Element you want to view</div>
</>
);
}
Reference : https://www.delftstack.com/howto/react/scroll-to-an-element-in-react/
I've to call useEffect / Fetch the data only when user click on Search Button otherwise not fetch the data..
Code:
const App = () => {
const[datas,setDatas] = useState([])
const [space,setSpace] = useState(null)
const [print, setPrint] = useState(false)
function getData(val){
// console.log(val.target.value)
setSpace(val.target.value);
setPrint(false)
}
// console.log(space)
useEffect(() => {
const fecthPosts = async () => {
let initial_url = `http://localhost:4000/search`
let url = initial_url + "?text=" + space
const res = await fetch(url);
const {result} = await res.json();
setDatas(result);
fecthPosts(); //I've to call this fetchPosts() when Btn is CLicked
},[space]);
return(
<div className="App">
{ //Displaying on search
print?
<>
<h2>{space}</h2>
<div>
{datas.map((field) =>
<p>{field.title}</p>
<p>{field.author}</p>
)}
</div>
</>
:null
}
<input type="text" onChange={getData} />
<button onClick={() => { setSpace(true); fetchPosts() }}>search</button>
</div>
)
}
};
export default App;
It's not working Error:
fetchPosts() is not defined...
I've also tried like this:
function trigger(){
useEffect(() => {
const fecthPosts = async () => {
let initial_url = `http://localhost:4000/search`
let url = initial_url + "?text=" + space
const res = await fetch(url);
const {result} = await res.json();
setDatas(result);
fecthPosts(); //I've to call this fetchPosts() when Btn is CLicked
},[space]);
}
<button onClick={() => { setSpace(true); trigger() }}>search</button>
It's not working Error:
React Hook useEffect has unnecessary dependencies:'space'
/PLZZ help to out...
make a separate function for api call and in your UseEffect function just call that function and on Button click function call the Api Function and it fetch data automatically
Use useCallback not useEffect
useCallback is similar to useEffect but is for when a function needs a callback, like what you're doing here onClick. useEffect is used in response to some prop changing not an action taken by a user.
You have to set your fetchPosts outside of the useEffect.
Then, you can use a new state search to track any click on the button.
const App = () => {
const [datas, setDatas] = useState([]);
const [space, setSpace] = useState(null);
const [print, setPrint] = useState(false);
const [search, setSearch] = useState(false);
const fetchPosts = async () => {
let initial_url = `http://localhost:4000/search`;
let url = initial_url + "?text=" + space;
const res = await fetch(url);
const { result } = await res.json();
setDatas(result);
};
function getData(val) {
setSpace(val.target.value);
setPrint(false);
}
useEffect(() => {
fetchPosts(); // fecthPosts is called each time space changed
}, [search]);
return (
<div className="App">
{
//Displaying on search
print ? (
<>
<h2>{space}</h2>
<div>
{datas.map((field) => (
<>
<p>{field.title}</p>
<p>{field.author}</p>
</>
))}
</div>
</>
) : null
}
<input type="text" onChange={getData} />
<button onClick={() => setSearch(!search)}>search</button>
</div>
);
};
export default App;
I initialized shouldFetchData = false. Once the button is clicked, I changed it's value; shouldFetchData = true. Inside useEffect I called fetchPosts() only when shouldFetchData is true.
import React, { useState } from "react";
const App = () => {
const [datas, setDatas] = useState([])
const [shouldFetchData, setShouldFetchData] = useState(false);
const [space, setSpace] = useState(null)
const [print, setPrint] = useState(false)
function getData(val) {
// console.log(val.target.value)
setSpace(val.target.value);
setPrint(false)
}
// console.log(space)
const fecthPosts = async () => {
let initial_url = `http://localhost:4000/search`
let url = initial_url + "?text=" + space
const res = await fetch(url);
const { result } = await res.json();
setDatas(result);
fecthPosts(); //I've to call this fetchPosts() when Btn is CLicked
}
useEffect(() => {
if(shouldFetchData) {
fecthPosts();
}
}, [space]);
return (
<div className="App">
{
print ?
<>
<h2>{space}</h2>
<div>
{datas.map((field) =>
<>
<p>{field.title}</p>
<p>{field.author}</p>
</>
)}
</div>
</>
: null
}
<input type="text" onChange={getData} />
<button onClick={() => {
setSpace(true);
setShouldFetchData(true);
}}>search</button>
</div>
)
};
export default App;
I found a few syntax errors in your code, so I hope I did what you intended.
If This is not the proper way to do this or if there exists a better way, please let me know. I'd be happy to learnš¤.
Here i want to render the AlertSound component when value of bitcoin state changes but it only render when i refresh application and not when value of state is changed. value of bitcoin state is getting update but the AlertSound component renders only once when app is reloaded
App.js:-
const App = () => {
const [bitcoin, setBitcoin] = useState();
const myRef = useRef("");
const fetchDetail = async () => {
const { data } = await Axios.get(
"https://api.coincap.io/v2/assets/bitcoin"
);
setBitcoin(Math.floor(data.data.priceUsd));
};
useEffect(() => {
const interval = setInterval(() => {
fetchDetail();
}, 1000);
return () => clearInterval(interval);
}, []);
return (
<div>
<h1>{bitcoin}</h1>
{bitcoin < 41405 ? <AlertSound /> : " "}
</div>
);
};
export default App;
AlertSound.js:-
const AlertSound = () => {
const myRef = useRef(null);
return (
<div>
<audio ref={myRef} src={SoundFile} autoPlay />
</div>
);
};
Add dependency of bitcoin state in useEffect
const App = () =>{
const [bitcoin, setBitcoin] = useState();
const myRef = useRef("");
const fetchDetail = async () =>{
const {data} = await Axios.get("https://api.coincap.io/v2/assets/bitcoin");
setBitcoin(Math.floor(data.data.priceUsd));
}
useEffect(() => {
const interval = setInterval(() => {
fetchDetail();
}, 1000);
return () => clearInterval(interval);
}, [bitcoin]);
return(
<div>
<h1>{bitcoin}</h1>
{bitcoin < 41405 ? <AlertSound/> : " "}
</div>
)
}
export default App;
I'm building my site in React and I have created pagination and search. When I search for something on the site, it only works when after that I go to another page. I think this is due to the fact that Softwares and Pagination are in the same component.
Then I tried lifting-state-up, but I got an error: React Minified Error # 31.
Here's Pagination component:
const Paginator = ({
total, // Total records
startPage = 1,
totalPages = null,
onMovePage = null,
}) => {
...
return (
<>
<section id={styles.paginator}>
<Header/>
...
{range(1, totalPages+1).map(p => (
<PagItem key={p} handleClick={ () => {setCurrentPage(p); onMovePage && onMovePage({currentPage: p})} } title={p} name={p} />
))}
...
</section>
</>
);
};
Here's Softwares component:
const Softwares = ({ search }) => {
const [softwares, setSoftwares] = useState([]);
const [total, setTotal] = useState(null);
const [totalPages, setTotalPages] = useState(null);
const [valid, setValid] = useState(false);
const fetchData = async ({ currentPage }) => {
const SEARCH = search ? `?search=${search}` : '';
const CURRENT_PAGE = currentPage && SEARCH === '' ? `?page=${currentPage}` : '';
const response = await fetch(`http://127.0.0.1:8000/api/software/${CURRENT_PAGE}${SEARCH}`);
const data = await response.json();
setSoftwares(data.results);
setTotal(data.count);
setTotalPages(data.total_pages);
setValid(true);
}
useEffect(() => {
fetchData({ currentPage: 1 });
}, []);
return (
<>
{
valid &&
<section className={styles.softwares}>
<Header header={"new softwares"} />
{softwares.map(s => (
<Article key={s.id} pathname={s.id} title={s.title} image={s.image} pubdate={s.pub_date} icon={s.category.parent.img} categoryID={s.category.id} categoryName={s.category.name} dCount={s.counter} content={s.content} />
))}
<Paginator totalPages={totalPages} total={total} onMovePage={fetchData} />
</section>
}
</>
);
};
SearchForm in Header component:
const Header = ({ handleChange, handleClick }) => {
return (
...
<SearchForm handleChange={handleChange} handleClick={handleClick} />
...
);
};
const SearchForm = ({ style, handleChange, handleClick }) => {
return (
<div style={style}>
<form>
<input
type="text"
onChange={handleChange}
/>
<SearchButton onClick={handleClick} />
<small>ENTER</small>
</form>
</div>
);
};
const SearchButton = ({onClick }) => {
return (
<button type="button" onClick={onClick}>
<FontAwesomeIcon icon={faSearch} />
</button>
);
};
And part of Search in App component:
const App = () => {
...
// Search
const [search, setSearch] = useState('');
const [shouldFetch, setShouldFetch] = useState(false);
const handleChange = (e) => {
setSearch(e.target.value);
}
useEffect(() => {
if (shouldFetch) {
(async () => {
const response = await fetch(`http://127.0.0.1:8000/api/software/?search=${search}`);
const data = await response.json();
setShouldFetch(false);
})()
}
}, [shouldFetch]);
const handleClick = () => setShouldFetch(true);
return (
<div className="App">
<Header handleChange={handleChange} handleClick={handleClick} />
...
<Switch>
<Route path="/" exact render={props => <Softwares {...props} search={search} />} />
</Switch>
{/* Actually I'd like to use Paginator here, but it
throws the error: React Minified Error # 31 */}
...
</div>
);
}
So, how can this be done?
The problem is your useEffect dependencies (or lack thereof).
Here's the relevant section of the code:
const Softwares = ({ search }) => {
const [softwares, setSoftwares] = useState([]);
const [total, setTotal] = useState(null);
const [totalPages, setTotalPages] = useState(null);
const [valid, setValid] = useState(false);
const fetchData = async ({ currentPage }) => {
const SEARCH = search ? `?search=${search}` : '';
const CURRENT_PAGE = currentPage && SEARCH === '' ? `?page=${currentPage}` : '';
const response = await fetch(`http://127.0.0.1:8000/api/software/${CURRENT_PAGE}${SEARCH}`);
const data = await response.json();
setSoftwares(data.results);
setTotal(data.count);
setTotalPages(data.total_pages);
setValid(true);
}
useEffect(() => {
fetchData({ currentPage: 1 });
}, []);
The empty dependency array means that you are running the effect that calls fetchData one time when the component mounts. Clicks in the Pagination component will call the fetchData function directly. Changes to search do not cause fetchData to re-run. The data depends on the search so search should be a dependency.
The fetchData function is fine in this component. The state that I would recommend lifting up is to lift the currentPage up from Pagination into Softwares. The onMovePage callback can just update the currentPage state. That way you can call fetchData only through your effect and run the effect whenever either search or currentPage changes.
const Softwares = ({ search }) => {
const [softwares, setSoftwares] = useState([]);
const [total, setTotal] = useState(null);
const [totalPages, setTotalPages] = useState(null);
const [valid, setValid] = useState(false);
const [currentPage, setCurrentPage] = useState(1);
useEffect(() => {
// defining the function inside of the useEffect
// lets eslint exhaustive dependency checks work their magic
const fetchData = async () => {
const SEARCH = search ? `?search=${search}` : '';
const CURRENT_PAGE = currentPage && SEARCH === '' ? `?page=${currentPage}` : '';
const response = await fetch(`http://127.0.0.1:8000/api/software/${CURRENT_PAGE}${SEARCH}`);
const data = await response.json();
setSoftwares(data.results);
setTotal(data.count);
setTotalPages(data.total_pages);
setValid(true);
}
// need to define and call in separate steps when using async functions
fetchData();
}, [currentPage, search]);
return (
...
<Paginator page={currentPage} totalPages={totalPages} total={total} onMovePage={setCurrentPage} />
...
);
};