I set up a logic to create pagination in react using data that I fetched from an endpoint API, which contains 5 objects that I displayed in this UI, Then I created a function pageCount that I implemented into my react paginate component, when I click to move to the second page it moved to the last page, next, and previously they didn't work either.
I research a lot to fix the problem but I didn't find any solutions?
Where this problem comes from?
Thank you
function App() {
//states
const [yogaCourses, setYogaCourses] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [levels, setLevels] = useState([]);
const [level, setLevel] = useState('');
const [pageNumber, setPageNumber] = useState(0);
//Paginate react
const coursePerPage = 2;
// const PageVisited = numberPage * coursePerPage;
const pageCount = Math.ceil(yogaCourses.length / coursePerPage);
console.log(pageCount);
//Track changes on each numberPage and display the data
useEffect(()=> {
setYogaCourses(yogaCourses.slice(pageNumber * 2 , (pageNumber + 1) * 2))
},[pageNumber]);
//To the next Page
const pageChange = ({selected}) => {
setPageNumber(selected);
}
//levelChangeHandler
const levelChangeHandler = ({value}) => {
setLevel(value);
}
//Filter by Levels // stateless
const filterLevels = (level) => {
return yogaCourses.filter((singleLevel)=> level ? singleLevel.level === level : true);
}
//Function to fetch the data from the API
const GetCourses = async () => {
const response = await axios.get(url)
const {data} = response;
return data;
}
//UseEffect to run the function on every render
useEffect(()=> {
const GetCoursesYoga = async () => {
const result = await GetCourses();
setYogaCourses(result);
setLevels(Array.from(new Set(result.map((result)=> result.level))));
}
GetCoursesYoga();
}, []);
//check if the we got response
useEffect(()=> {
if(yogaCourses.length > 0) {
setIsLoading(false);
}
}, [yogaCourses])
if(isLoading) {
return (
<Loading/>
)
}
else {
return (
<main>
<div className="title">
<h2>YOUR PRACTICE REIMAGINED</h2>
</div>
<LevelsFilter levels={levels} onChange={levelChangeHandler}/>
<YogaCourses yogaCourses= {(filterLevels(level)).slice(0, 2)}/>
<ReactPaginate
previousLabel = {"Previous"}
nextLabel = {"Next"}
pageCount = {pageCount}
onPageChange= {pageChange}
/>
</main>
);
}
}
export default App;
Related
I'm learning React Native and can't understand how to make function or component instead of repeated part of my code:
export const PostScreen = () => {
const postId = 1
/* THIS PART IS REPEATED IN MANY FILES */
const [data, setData] = useState([]);
const [loader, setLoader] = useState(false);
const [error, setError] = useState(null);
const fetchData = async () => {
setError(null)
setLoader(true)
try {
const data = await Api.get(`http://localhost/api/posts/${postId}`)
if (data.success == 1) {
setData(data.data)
}
else {
setError(data.error)
}
}
catch(e) {
console.log(e)
setError('Some problems')
}
finally {
setLoader(false)
}
}
useEffect(() => {
fetchData()
}, [])
if (loader) {
return <Loader />
}
if (error) {
return <View><Text>{error}</Text></View>
}
/*END>>> THIS PART IS REPEATED IN MANY FILES */
return (
<View><Text>{data.title}</Text></View>
)
}
The problem is that fetchData is working with state. I found, how to do it with Context, but I don't wont to use it. Is there any way to do clear code without Context?
So, why not make your own hook, then?
Write the hook in a dedicated module:
function useMyOwnHook(props) {
const {
postId,
} = props;
const [data, setData] = useState([]);
const [loader, setLoader] = useState(false);
const [error, setError] = useState(null);
const fetchData = async () => {
setError(null)
setLoader(true)
try {
const data = await Api.get(`http://localhost/api/posts/${postId}`)
if (data.success == 1) {
setData(data.data)
}
else {
setError(data.error)
}
}
catch(e) {
console.log(e)
setError('Some problems')
}
finally {
setLoader(false)
}
}
useEffect(() => {
fetchData()
}, [])
const render = loader
? <Loader />
: error
? <View><Text>{error}</Text></View>
: null;
return {
data,
render,
}
}
At that point, the component will be written as follows:
export const PostScreen = () => {
const postId = 1
const {
render,
data,
} = useMyOwnHook({ postId });
return render ?? (
<View><Text>{data.title}</Text></View>
)
}
I have a function that filters the customers based on their levels (intermediate, beginner ), I'm passing this function through a component that has React select to filter my Data(async)
The filter is working only when I filter the first time but when I choose another value to filter it gave me a blank page?
I tried useEffect to keep it updated but it not working
Do you have any suggestions?
//APP.js
import React,{useState, useEffect} from "react";
import YogaCourses from "./components/YogaCourses/YogaCourses";
import Loading from "./components/IsLoading/Loading";
import LevelsFilter from './components/LevelsFilter/LevelsFilter';
//API to fetch the data
const url = 'https://gist.githubusercontent.com/Tayarthouail/8fb14fe117fdd718ceabd6ee05ed4525/raw/8c86c4bb89fc51667ba0578b2dcba14a0b21f08c/Yoga-courses-api.json';
function App() {
//states
const [yogaCourses, setYogaCourses] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [levels, setLevels] = useState([]);
//Filter by Levels
const filterLevels = (level) => {
const getLevels = yogaCourses.filter((singleLevel)=> singleLevel.level === level.value);
setYogaCourses(getLevels);
}
//Function to fetch the data from the API
const GetCourses = async () => {
const response = await axios.get(url)
const {data} = response;
return data;
}
//UseEffect to run the function on every render
useEffect(()=> {
const GetCoursesYoga = async () => {
const result = await GetCourses();
setYogaCourses(result);
console.log(result);
setLevels(Array.from(new Set(result.map((result)=> result.level))));
}
GetCoursesYoga();
}, []);
//check if the we got response
useEffect(()=> {
if(yogaCourses.length > 0) {
setIsLoading(false);
}
}, [yogaCourses])
if(isLoading) {
return (
<Loading/>
)
}
else {
return (
<main>
<div className="title">
<h2>YOUR PRACTICE REIMAGINED</h2>
</div>
<LevelsFilter levels={levels} filterLevels={filterLevels}/>
<YogaCourses yogaCourses= {yogaCourses}/>
</main>
);
}
}
export default App;
//LevelsFilter component
import React from 'react';
import Select from 'react-select';
import './LevelsFilter.css';
const LevelsFilter = ({levels, filterLevels}) => {
const option = levels.map((level)=> ({value : level, label: level}));
return (
<div>
<Select
options ={option}
className="select-option"
placeholder={"Type..."}
onChange={filterLevels}
/>
</div>
)
}
export default LevelsFilter;
Issue
You are replacing your state with the filtered data and subsequent filtering filters from there, so you only ever reduce your data.
Solution
I suggest storing an active filter state (i.e. level) and do the filtering inline when rendering so you skip the issue of stale/bad state.
function App() {
//states
const [yogaCourses, setYogaCourses] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [levels, setLevels] = useState([]);
const [level, setLevel] = useState('');
const levelChangeHandler = ({ value }) => {
setLevel(value);
}
//Filter by Levels
const filterLevels = (level) => {
return yogaCourses.filter(
(singleLevel) => level ? singleLevel.level === level : true
);
}
...
if(isLoading) {
return (
<Loading/>
)
}
else {
return (
<main>
<div className="title">
<h2>YOUR PRACTICE REIMAGINED</h2>
</div>
<LevelsFilter levels={levels} onChange={levelChangeHandler}/>
<YogaCourses yogaCourses={filterLevels(level)}/>
</main>
);
}
}
LevelsFilter
import React from 'react';
import Select from 'react-select';
import './LevelsFilter.css';
const LevelsFilter = ({ levels, onChange }) => {
const option = levels.map((level)=> ({value : level, label: level}));
return (
<div>
<Select
options ={option}
className="select-option"
placeholder={"Type..."}
onChange={onChange}
/>
</div>
)
}
You need a copy state.
Your code is replacing the data source with filtered data. When you first time selects the option then your state replaces it with that one and you no longer have previous state data. On the second time, you don't have data that why it's blank on-screen.
Just copy and replace the below app.js code:
import React,{useState, useEffect} from "react";
import YogaCourses from "./components/YogaCourses/YogaCourses";
import Loading from "./components/IsLoading/Loading";
import LevelsFilter from './components/LevelsFilter/LevelsFilter';
//API to fetch the data
const url = 'https://gist.githubusercontent.com/Tayarthouail/8fb14fe117fdd718ceabd6ee05ed4525/raw/8c86c4bb89fc51667ba0578b2dcba14a0b21f08c/Yoga-courses-api.json';
function App() {
//states
const [yogaCourses, setYogaCourses] = useState([]);
const [filteredYogaCourses, setFillteredYogaCourses] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [levels, setLevels] = useState([]);
//Filter by Levels
const filterLevels = (level) => {
const getLevels = yogaCourses.filter((singleLevel)=> singleLevel.level === level.value);
setFillteredYogaCourses(getLevels);
}
//Function to fetch the data from the API
const GetCourses = async () => {
const response = await axios.get(url)
const {data} = response;
return data;
}
//UseEffect to run the function on every render
useEffect(()=> {
const GetCoursesYoga = async () => {
const result = await GetCourses();
setYogaCourses(result);
setLevels(Array.from(new Set(result.map((result)=> result.level))));
}
GetCoursesYoga();
}, []);
//check if the we got response
useEffect(()=> {
if(yogaCourses.length > 0) {
setIsLoading(false);
}
}, [yogaCourses])
if(isLoading) {
return (
<Loading/>
)
}
else {
return (
<main>
<div className="title">
<h2>YOUR PRACTICE REIMAGINED</h2>
</div>
<LevelsFilter levels={levels} filterLevels={filterLevels}/>
<YogaCourses yogaCourses= {filteredYogaCourses}/>
</main>
);
}
}
export default App;
I hope it will work, if not then please debug it because I haven't tested it but the idea will be same. :)
*** The question is quite simple, I just wrote a lot to be specific. ***
I've been looking online for a few hours, and I can't seem to find answer. Most pagination is about after you have received the data from the API call, or for backend node.js built with it's own server.
My issue, I have an API request that returns an array of 500 ID's. Then a second multi API call, looping through each ID making a promise API call. I use the Promise.all method.
It takes 2-3 minutes to complete this request.
Currently, I made a quick filter to get the first ten results, so it'll display and I can render the data to work on other things like the render component and styling.
My question, I'd like to be able to paginate the data while API calls are still being made.
Basically, Promise.all send an array of 10 id's (ten API calls), get continually. But after the first set of ten, I'd like to start receiving the data to render.
Right now, I can only get ten with my filter method. Or wait 2-3 min for all 500 to render.
Here is my request.js file, (it's part of my App component, I just separated it for clarity).
import axios from 'axios';
import BottleNeck from 'bottleneck'
const limiter = new BottleNeck({
maxConcurrent: 1,
minTime: 333
})
export const Request = (setResults, searchBarType, setLoading) => {
const searchBar = type => {
const obj = {
'new': 'newstories',
'past': '',
'comments': 'user',
'ask': 'askstories',
'show': 'showstories',
'jobs': 'jobstories',
'top': 'topstories',
'best': 'beststories',
'user': 'user'
}
return obj[type] ? obj[type] : obj['new'];
}
let type = searchBar(searchBarType)
const getData = () => {
const options = type
const API_URL = `https://hacker-news.firebaseio.com/v0/${options}.json?print=pretty`;
return new Promise((resolve, reject) => {
return resolve(axios.get(API_URL))
})
}
const getIdFromData = (dataId) => {
const API_URL = `https://hacker-news.firebaseio.com/v0/item/${dataId}.json?print=pretty`;
return new Promise((resolve, reject) => {
return resolve(axios.get(API_URL))
})
}
const runAsyncFunctions = async () => {
setLoading(true)
const {data} = await getData()
let firstTen = data.filter((d,i) => i < 10);
Promise.all(
firstTen.map(async (d) => {
const {data} = await limiter.schedule(() => getIdFromData(d))
console.log(data)
return data;
})
)
.then((newresults) => setResults((results) => [...results, ...newresults]))
setLoading(false)
// make conditional: check if searchBar type has changed, then clear array of results first
}
runAsyncFunctions()
}
and helps, here's my App.js file
import React, { useState, useEffect } from 'react';
import './App.css';
import { SearchBar } from './search-bar';
import { Results } from './results';
import { Request } from '../helper/request'
import { Pagination } from './pagination';
function App() {
const [results, setResults] = useState([]);
const [searchBarType, setsearchBarType] = useState('news');
const [loading, setLoading] = useState(false);
const [currentPage, setCurrentPage] = useState(1);
const [resultsPerPage] = useState(3);
// Select search bar button type
const handleClick = (e) => {
const serachBarButtonId = e.target.id;
console.log(serachBarButtonId)
setsearchBarType(serachBarButtonId)
}
// API calls
useEffect(() => {
Request(setResults, searchBarType, setLoading)
}, [searchBarType])
// Get current results
const indexOfLastResult = currentPage * resultsPerPage;
const indexOfFirstResult = indexOfLastResult - resultsPerPage;
const currentResults = results.slice(indexOfFirstResult, indexOfLastResult);
// Change page
const paginate = (pageNumber) => setCurrentPage(pageNumber);
return (
<div className="App">
<SearchBar handleClick={handleClick} />
<Results results={currentResults} loading={loading} />
<Pagination resultsPerPage={resultsPerPage} totalResults={results.length} paginate={paginate} />
</div>
);
}
export default App;
I hope it's generic looking enough to follow guide lines. Please ask me anything to help clarify. I've spent 8-10 hours searching and attempting to solve this...
You can continue with your filter, but you have to do some changes, for totalResults props of the component Pagination you have to set 500 rows so the user can select the page he wants because if you set 10 rows, the pages a user can select are 1,2,3,4, but we don't need that we need to put all pages 1 to 34 pages because we have 500 ids. The second point, we need to fetch data from the server by page with a page size equal to 3 we need to pass to Request startIndex and lastIndex to Request.
Request.js
import axios from 'axios';
import BottleNeck from 'bottleneck'
const limiter = new BottleNeck({
maxConcurrent: 1,
minTime: 333
})
export const Request = (setResults, searchBarType, setLoading, startIndex, lastIndex) => {
const searchBar = type => {
const obj = {
'new': 'newstories',
'past': '',
'comments': 'user',
'ask': 'askstories',
'show': 'showstories',
'jobs': 'jobstories',
'top': 'topstories',
'best': 'beststories',
'user': 'user'
}
return obj[type] ? obj[type] : obj['new'];
}
let type = searchBar(searchBarType)
const getData = () => {
const options = type
const API_URL = `https://hacker-news.firebaseio.com/v0/${options}.json?print=pretty`;
return new Promise((resolve, reject) => {
return resolve(axios.get(API_URL))
})
}
const getIdFromData = (dataId) => {
const API_URL = `https://hacker-news.firebaseio.com/v0/item/${dataId}.json?print=pretty`;
return new Promise((resolve, reject) => {
return resolve(axios.get(API_URL))
})
}
const runAsyncFunctions = async () => {
setLoading(true)
const {data} = await getData()
let ids = data.slice(firstIndex, lastIndex+1) // we select our ids by index
Promise.all(
ids.map(async (d) => {
const {data} = await limiter.schedule(() => getIdFromData(d))
console.log(data)
return data;
})
)
.then((newresults) => setResults((results) => [...results, ...newresults]))
setLoading(false)
// make conditional: check if searchBar type has changed, then clear array of results first
}
runAsyncFunctions()
}
App.js
import React, { useState, useEffect } from 'react';
import './App.css';
import { SearchBar } from './search-bar';
import { Results } from './results';
import { Request } from '../helper/request'
import { Pagination } from './pagination';
function App() {
const [results, setResults] = useState([]);
const [searchBarType, setsearchBarType] = useState('news');
const [loading, setLoading] = useState(false);
const [currentPage, setCurrentPage] = useState(1);
const [resultsPerPage] = useState(3);
// Select search bar button type
const handleClick = (e) => {
const serachBarButtonId = e.target.id;
console.log(serachBarButtonId)
setsearchBarType(serachBarButtonId)
}
// API calls
useEffect(() => {
Request(setResults, searchBarType, setLoading, 0, 2) //we fetch the first 3 articles
}, [searchBarType])
// Change page
const paginate = (pageNumber) => {
// Get current results
const indexOfLastResult = currentPage * resultsPerPage;
const indexOfFirstPost = indexOfLastResult - resultsPerPage;
Request(setResults, searchBarType, setLoading, indexOfFirstPost , indexOfLastResult) //we fetch the 3 articles of selected page
setCurrentPage(pageNumber);
}
return (
<div className="App">
<SearchBar handleClick={handleClick} />
<Results results={results} loading={loading} />
<Pagination resultsPerPage={resultsPerPage} totalResults={500} paginate={paginate} />
</div>
);
}
export default App;
Hello my pagination is working if I change the URL manually example /diet/1, 2 or 3. I've implemented a function to add page + 1 and onClick the numbers are changing on the URL but the page is not loading the next content? My code below: Any kind of help will be appreciated. Thanks
const Survey = props => {
const history = useHistory()
const [selected, setSelected] = useState({})
const { count, setCount, percentage, setPercentage } = useContext(AppContext)
const pageId = parseInt(props.pageId)
const [page, setPage] = useState(pageId)
const [results, setResults] = useState([])
const [totalPages, setTotalPages] = useState(5)
useEffect(() => {
surveyApi()
return () => {}
}, [])
const surveyApi = async () => {
const response = await fetch(`/api/surveyoptions?page=${page}`)
const results = await response.json()
setResults(results.result)
setPage(page)
setTotalPages(page)
}
const getPageLink = ( pageNo ) => {
return `/diet/${ pageNo }`
}
return (
<>
<Link
to={getPageLink(page + 1)}
onClick={ () => setPage(page + 1) }
className="next">
Click Link
</Link>
</>
)
}
export default Survey
const Survey = props => {
const history = useHistory()
const [selected, setSelected] = useState({})
const { count, setCount, percentage, setPercentage } = useContext(AppContext)
const pageId = parseInt(props.pageId)
const [page, setPage] = useState(pageId)
const [results, setResults] = useState([])
const [totalPages, setTotalPages] = useState(5)
useEffect(() => {
surveyApi()
}, [page]) // <--- pass `page` here.
const surveyApi = async () => {
const response = await fetch(`/api/surveyoptions?page=${page}`)
const results = await response.json()
setResults(results.result)
setPage(page)
setTotalPages(page)
}
const getPageLink = ( pageNo ) => {
return `/diet/${ pageNo }`
}
return (
<>
<Link
to={getPageLink(page + 1)}
onClick={ () => setPage(page + 1) }
className="next">
Click Link
</Link>
</>
)
}
export default Survey
I want to split an array into two arrays.
The problem is main array which I want to split into two is coming from server.And I need to wait until it loads.
Here my code.
This is useSafeFetch custom Hook which is responsible to fetch data (by the way this is working fine just paste here to show you all code)
const useSafeFetch = (url) => {
const [data, setData] = useState([]);
const [customUrl] = useState(url);
const [isLoading, setIsLoading] = useState(false);
const [isError, setIsError] = useState(false);
useEffect(_ => {
let didCancel = false;
const fetchData = async () => {
if(didCancel === false){
setIsError(false);
setIsLoading(true);
}
try {
const result = await axios(customUrl);
if(didCancel === false){
setData(result.data);
}
} catch (error) {
if(didCancel === false){
setIsError(true);
}
}
if(didCancel === false){
setIsLoading(false);
}
};
fetchData();
return () => {
didCancel = true;
};
}, []);
return {
data,
isLoading,
isError,
}
}
I try to write a function which return a two independent array
export default _ => {
const {data,isLoading,isError} = useSafeFetch(`my api`);
useEffect(_ => {
console.log(data); // length 11
const mainA = splitTwoArrays(data);
console.log("progress",mainA.progressHalf); //length 5
console.log("circle", mainA.circleHalf); //length 1
});
const splitTwoArrays = mainArr => {
const half = mainArr.length >>> 1;
let progressHalf = mainArr.splice(0, half);
let circleHalf = mainArr.splice(half, mainArr.length);
console.log(mainArr);
return {
progressHalf,
circleHalf,
}
}
return (
//do something with data
)
}
This is not worked correctly.
As you can see main data length is 11 but function splitTwoArrays split arrays with wrong way. progressHalf length is 5 another circleHalf is 1.But circleHalf need to 6.
Next try:
using useEffect
export default _ => {
const {data,isError,isLoading} = useSafeFetch(`my api`);
const [progressHalf,setProgressHalf] = useState([]);
const [newArr,setNewArr] = useState([]);
const [half,setHalf] = useState(0);
useEffect(_ => {
setHalf(data.length >>> 1);
setNewArr(data);
const partArr = newArr.slice(0, half);
setProgressHalf([...progressHalf, ...partArr]);
})
return (
//do something with data
)
}
This gets into infinity loop when I uncomment this part setProgressHalf([...progressHalf, ...partArr]);.
I try to give useEffect some dependency but unfortunately this also won't work.
I solve this on my own.
const { data } = useSafeFetch("https://jsonplaceholder.typicode.com/users");
const [copiedData, setCopiedData] = useState([]);
const [halfArr, setHalfArr] = useState([]);
const [secHalf, setSecHalf] = useState([]);
const [half, setHalf] = useState(0);
useEffect(
_ => {
setCopiedData([...data]);
setHalf(data.length >>> 1);
setHalfArr([...copiedData.slice(0, half)]);
setSecHalf([...copiedData.slice(half, copiedData.length)]);
},
[data, half]
);
console.log(halfArr);
console.log(secHalf);
And in the end you get two array which created from main data you get from server.
Codesandbox