Custom react hook triggers api call multiple times - reactjs

I cannot figure out how to handle my function components calling my api repeatedly. I have two components which retrieve data, one of them calls the api twice. Once before the second component once after.
I am using a custom react hook and axios get method to retrieve the data. My two components are are nested. The first component when loads and fetches data. Inside this component is a child component which when renders it fetches data right before passing the first set of data as props to another child component. When it completes loading it reloads the first child component which again calls the api for data. I understand the function components reload on state change. I would be happy for it to not call the api a second time. Is there a way to check if it already has data and bypass the api call?
Custom hook to retrieve data
import React, { useState, useEffect, useReducer } from "react";
import axios from "axios";
const dataFetchReducer = (state, action) => {
switch (action.type) {
case "FETCH_INIT":
return { ...state, isLoading: true, hasErrored: false };
case "FETCH_SUCCESS":
return {
...state,
isLoading: false,
hasErrored: false,
errorMessage: "",
data: action.payload
};
case "FETCH_FAILURE":
return {
...state,
isLoading: false,
hasErrored: true,
errorMessage: "Data Retrieve Failure"
};
case "REPLACE_DATA":
// The record passed (state.data) must have the attribute "id"
const newData = state.data.map(rec => {
return rec.id === action.replacerecord.id ? action.replacerecord : rec;
});
return {
...state,
isLoading: false,
hasErrored: false,
errorMessage: "",
data: newData
};
default:
throw new Error();
}
};
const useAxiosFetch = (initialUrl, initialData) => {
const [url] = useState(initialUrl);
const [state, dispatch] = useReducer(dataFetchReducer, {
isLoading: false,
hasErrored: false,
errorMessage: "",
data: initialData
});
useEffect(() => {
let didCancel = false;
const fetchData = async () => {
dispatch({ type: "FETCH_INIT" });
try {
let result = await axios.get(url);
if (!didCancel) {
dispatch({ type: "FETCH_SUCCESS", payload: result.data });
}
} catch (err) {
if (!didCancel) {
dispatch({ type: "FETCH_FAILURE" });
}
}
};
fetchData();
return () => {
didCancel = true;
};
}, [url]);
const updateDataRecord = record => {
dispatch({
type: "REPLACE_DATA",
replacerecord: record
});
};
return { ...state, updateDataRecord };
};
export default useAxiosFetch;
Main component which renders the "CompaniesDropdown" twice inside
CompaniesDropdown is one of three dropdowns within the ListFilterContainer component but the only one which calls the api more than once. The other two dropdowns load by selection of the CompaniesDropdown.
import React, { useMemo, useEffect, useContext } from "react";
import InvoiceList from "../src/Components/Lists/InvoiceList";
import useAxiosFetch from "../src/useAxiosFetch";
import { ConfigContext } from "./_app";
import ListFilterContainer from "../src/Components/Filters/InvoiceFilters";
// import "../css/ListView.css";
const Invoices = props => {
const context = useContext(ConfigContext);
useEffect(() => {
document.title = "Captive Billing :: Invoices";
});
const {
data,
isLoading,
hasErrored,
errorMessage,
updateDataRecord
} = useAxiosFetch("https://localhost:44394/Invoice/GetInvoices/false", []);
const newInvoicesList = useMemo(
() => data
// .filter(
// ({ sat, sun }) => (speakingSaturday && sat) || (speakingSunday && sun)
// )
// .sort(function(a, b) {
// if (a.firstName < b.firstName) {
// return -1;
// }
// if (a.firstName > b.firstName) {
// return 1;
// }
// return 0;
// }),
// [speakingSaturday, speakingSunday, data]
);
const invoices = isLoading ? [] : newInvoicesList;
if (hasErrored)
return (
<div>
{errorMessage} "Make sure you have launched "npm run json-server"
</div>
);
if (isLoading) return <div>Loading...</div>;
const dataProps = {
data: invoices,
titlefield: "invoiceNumber",
titleHeader: "Invoice Number:",
childPathRoot: "invoiceDetail",
childIdField: "invoiceId",
childDataCollection: "invoiceData"
};
var divStyle = {
height: context.windowHeight - 100 + "px"
};
return (
<main>
<ListFilterContainer />
<section style={divStyle} id="invoices" className="card-container">
<InvoiceList data={dataProps} />
</section>
</main>
);
};
Invoices.getInitialProps = async ({ req }) => {
const isServer = !!req;
return { isServer };
};
export default Invoices;
Actual result is described above. My main concern is to not have the api calls more than once.
Here is some additional code to help. It is the filter control mentioned above. It, as you will notice really just contains dropdowns and a text box. The first dropdown is the one that calls the api twice. The second two are not visible until that one is selected.
import React, { useState, useMemo } from "react";
import CompaniesDropdown from "../Dropdowns/CompaniesDropdown";
import LocationsDropdown from "../Dropdowns/LocationsDropdown";
import AccountsDropdown from "../Dropdowns/AccountsDropdown";
import Search from "./SearchFilter/SearchFilter";
const InvoiceFilters = props => {
const [company, setCompany] = useState("");
const [location, setLocation] = useState(undefined);
const [account, setAccount] = useState(undefined);
const handleClientChange = clientValue => {
setCompany(clientValue);
};
const handleLocationsChange = locationValue => {
setLocation(locationValue);
};
const handleAccountsChange = AccountValue => {
setAccount(AccountValue);
};
return (
<section className="filter-container mb-3">
<div className="form-row">
<div className="col-auto">
<CompaniesDropdown change={e => handleClientChange(e)} />
</div>
<div className="col-auto">
<LocationsDropdown
selectedCompany={company}
change={e => handleLocationsChange(e)}
/>
</div>
<div className="col-auto">
<AccountsDropdown
selectedCompany={company}
change={e => handleAccountsChange(e)}
/>
</div>
<div className="col-auto">
<Search />
</div>
</div>
</section>
);
};
InvoiceFilters.getInitialProps = async ({ req }) => {
const isServer = !!req;
return { isServer };
};
export default InvoiceFilters;
Also the datalist
import React from "react";
import Link from "next/link";
import InvoiceListRecord from "./InvoiceListRecord";
const InvoiceList = props => {
let dataCollection = props.data.data;
return dataCollection.length == 0 ? "" : dataCollection.map((item, index) => {
return (
<section key={"item-" + index} className="card text-left mb-3">
<header className="card-header">
<span className="pr-1">{props.data.titleHeader}</span>
<Link
href={
"/" +
props.data.childPathRoot +
"?invoiceId=" +
item[props.data.childIdField]
}
as={
"/" +
props.data.childPathRoot +
"/" +
item[props.data.childIdField]
}
>
<a>{item[props.data.titlefield]}</a>
</Link>{" "}
</header>
<div className="card-body">
<div className="row">
<InvoiceListRecord
data={item}
childDataCollection={props.data.childDataCollection}
/>
</div>
</div>
</section>
);
});
};
InvoiceList.getInitialProps = async ({ req }) => {
console.log("Get Intitial Props works: Invoices Page!");
const isServer = !!req;
return { isServer };
};
export default InvoiceList;
and the list items component.
import React from "react";
const InvoiceListRecord = props => {
var invoiceData = JSON.parse(props.data[props.childDataCollection]);
return invoiceData.map((invKey, index) => {
return (
<div className="col-3 mb-1" key={"item-data-" + index}>
<strong>{invKey.MappedFieldName}</strong>
<br />
{invKey.Value}
</div>
);
});
};
export default InvoiceListRecord;

The API is not called more than once if the url is the same. It just gets the value from data variable. The api call is not made again, unless the url changes.
I created an example from your code, changing all the unknown components to div. I added a console.log in the useEffect of the useAxiosFetch hook. And to re-render the component, I added a button to increment the count.
You'll see that the console.log from the hook is printed only once, even though the component re-renders on every button click. The value just comes from the data variable from the hook and the api call is not made again and again.

Related

How to avoid state reset while using Intersection observer in React.js?

I'm trying to implement intersection observer in react functional component.
import React, { useEffect, useRef, useState } from "react";
import { getData } from "./InfiniteClient";
export default function InfiniteScroll() {
const [data, setData] = useState([]);
const [pageCount, setPageCount] = useState(1);
const sentinal = useRef();
useEffect(() => {
const observer = new IntersectionObserver(intersectionCallback);
observer.observe(sentinal.current, { threshold: 1 });
getData(setData, data, pageCount, setPageCount);
}, []);
const intersectionCallback = (entries) => {
if (entries[0].isIntersecting) {
setPageCount((pageCount) => pageCount + 1);
getData(setData, data, pageCount, setPageCount);
}
};
return (
<section>
{data &&
data.map((photos, index) => {
return <img alt="" src={photos.url} key={index} />;
})}
<div className="sentinal" ref={sentinal}>
Hello
</div>
</section>
);
}
When I'm consoling prevCount above or prevData in the below function is coming as 1 and [] which is the default state.
function getData(setData, prevData, pageCount, setPageCount) {
fetch(
`https://jsonplaceholder.typicode.com/photos?_page=${pageCount}&limit=10`
)
.then((val) => val.json())
.then((val) => {
console.log("prevD", prevData,val,pageCount);
if (!prevData.length) setData([...val]);
else {
console.log("Here", pageCount, prevData, "ddd", val);
setData([...prevData, ...val]);
}
}).catch((e)=>{
console.log("Error",e);
});
}
export { getData };
The code is not entering the catch block. I have also tried setPageCount(pageCount=> pageCount+ 1); and setPageCount(pageCount+ 1); gives same result. What am I doing wrong?
Code Sandbox
Edit: I converted the above code to class based component and it is working fine. I'm more curious on how hooks based approach is resets the states.
import React, { Component } from "react";
export default class InfiniteClass extends Component {
constructor() {
super();
this.state = {
pageCount: 1,
photos: []
};
}
getData = () => {
fetch(
`https://jsonplaceholder.typicode.com/photos?_page=${this.state.pageCount}&limit=3`
)
.then((val) => val.json())
.then((val) => {
this.setState({
photos: [...this.state.photos, ...val],
pageCount: this.state.pageCount + 1
});
})
.catch((e) => {
console.log("Error", e);
});
};
componentDidMount() {
console.log(this.sentinal);
const observer = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting) {
this.getData();
}
});
observer.observe(this.sentinal, { threshold: 1 });
}
render() {
return (
<section>
{this.state &&
this.state.photos.length &&
this.state.photos.map((photo, index) => {
return <img alt="" src={photo.url} key={index} />;
})}
<div
className="sentinal"
ref={(sentinal) => (this.sentinal = sentinal)}
>
Hello
</div>
</section>
);
}
}
Edit 2 : I tried consoling pageCount at two places one above IntersectionCallback and one inside. The value inside is not changing meaning it is storing its own variables.
useState in react takes either argument or function. So, I did something hackish. It is working but I'm looking for a better aproach.
const intersectionCallback = (entries) => {
if (entries[0].isIntersecting) {
setPageCount((pageCount) => {
setData(d=>{
getData(setData, d, pageCount);
return d;
})
return pageCount + 1;
});
}
};

value of state increment twice in reducer.js file

Order is an array of Objects and there is a key 'count' inside each variable. When ADD_ITEM_IN_ORDER case is executed than the count of particular object should be increment by 1. But, in this case, when the particular item is already present in the array than the value of count of that item incrementing by 2, which should not happen(it should increment by 1).
reducer.js
export const initialState = {
Order: [],
};
const reducer = (state, action) => {
console.log(action);
switch (action.type) {
case "ADD_ITEM_IN_ORDER":
const tempOrder1 = [...state.Order];
const index1 = state.Order.findIndex((item) => item.id === action.item.id);
if (index1 >= 0) {
console.log("before",tempOrder1[index1].Count);
tempOrder1[index1].Count += 1;
return { ...state, Order: tempOrder1 };
}
else {
console.log("New item added");
return {
...state,
Order: [...state.Order, action.item]
};
}
default:
return state;
}
};
export default reducer;
action file
import React from 'react';
import VegIcon from '../Images/VegIcon.png';
import NonVegIcon from '../Images/NonVegIcon.png';
import { useStateValue } from '../StateProvider';
import { db } from '../firebase';
const CartMenu = (props) => {
const [{ Order }, dispatch] = useStateValue();
const add = () => {
dispatch({
type: "ADD_ITEM_IN_ORDER",
item: {
id: props.id,
menuCollectionName:props.menuCollectionName,
VegNonV: props.VegNonV,
Menu: props.Menu,
Price: props.Price,
Count: 1,
// RestuarantId: props.restuarantId
}
});
console.log(Order);
};
const remove = () => {
dispatch({
type: "REMOVE_ITEM_FROM_ORDER",
item: {
id: props.id, // id of a dish
restuarantId: props.restuarantId
}
});
};
return (
<div className='Menu_Display'>
<div className='Menu_Display_subsection1'>
{props.VegNonV === "Veg" ?
<img className="Menu_Veg_NonVeg" src={VegIcon} />
:
<img className="Menu_Veg_NonVeg" src={NonVegIcon} />
}
<div className='Menu_Name'>{props.Menu}</div>
</div>
<div className="Menu_Add_Button" >
<div className="Menu_minus" onClick={remove}>−</div>
<span>{props.Count}</span>
<div className="Menu_plus" onClick={add}>+</div>
</div>
<div className='Menu_Price'>
<span></span>
<span>₹{Math.round(props.Price * props.Count * 100) / 100}</span>
</div>
</div>
);
};
export default CartMenu;
StateProvider.js
//setup data layer
// we need this to track the basket data
import React,{createContext,useContext,useReducer} from 'react';
export const StateContext = createContext();
//Build Provider
export const StateProvider = ({reducer,initialState,children}) =>
(
<StateContext.Provider value = {useReducer(reducer,initialState)}>
{children}
</StateContext.Provider>
);
export const useStateValue = () => useContext(StateContext);
The context API broadcasts updates when it notices a change in the value. Since you are invoking useReducer within the value props, that returns an array (state value and dispatch function), it is this, that is likely causing double dispatch. I recommend you re-write your Provider logic.
import React,{createContext,useContext,useReducer} from 'react';
export const StateContext = createContext({ //Make sure to export this
Order:[], //Will be consuimg the state value from here
addItem:(arg)=>{} //This function will be hooked to a dispatch function below
});
//Build Provider
export const StateProvider = ({reducer,initialState,children}) =>
{
const [state,dispatchFn] = useReducer(reducer,initialState)
const addItemHandler = (item) => {
dispatchFn(item)
}
return(<StateContext.Provider value = {{Order:state.Order,addItem:addItemHandler}}>
{children}
</StateContext.Provider>)
};
You can then wrap your root component with the context provider component StateProvider so that all the components can access the state values.
In your index.js wrap the component like this:
import {StateProvider} from './path/to/provider'
ReactDOM.render(<StateProvider><App/></StateProvider>,doucment.getElementById("root"))
You can then use the context state and dispatch function from useContext hook by, passing the context variable.
In your action file:
import {useContext},React from 'react'; //Import use context
import {StateContext} from './path/to/state/context'
import VegIcon from '../Images/VegIcon.png';
import NonVegIcon from '../Images/NonVegIcon.png';
import { db } from '../firebase';
const CartMenu = (props) => {
const order_ctx = useContext(StateContext);
const add = () => {
order_ctx.addItem({
type: "ADD_ITEM_IN_ORDER",
item: {
id: props.id,
menuCollectionName:props.menuCollectionName,
VegNonV: props.VegNonV,
Menu: props.Menu,
Price: props.Price,
Count: 1,
// RestuarantId: props.restuarantId
}
});
console.log(Order);
};
const remove = () => {
order_ctx.rmItem({ //Note: rmItem method is not added to createContext, but this is just to demonstrate how state update methods can be invoked by using the context instance.
type: "REMOVE_ITEM_FROM_ORDER",
item: {
id: props.id, // id of a dish
restuarantId: props.restuarantId
}
});
};
return (
<div className='Menu_Display'>
<div className='Menu_Display_subsection1'>
{props.VegNonV === "Veg" ?
<img className="Menu_Veg_NonVeg" src={VegIcon} />
:
<img className="Menu_Veg_NonVeg" src={NonVegIcon} />
}
<div className='Menu_Name'>{props.Menu}</div>
</div>
<div className="Menu_Add_Button" >
<div className="Menu_minus" onClick={remove}>−</div>
<span>{props.Count}</span>
<div className="Menu_plus" onClick={add}>+</div>
</div>
<div className='Menu_Price'>
<span></span>
<span>₹{Math.round(props.Price * props.Count * 100) / 100}</span>
</div>
</div>
);
};
export default CartMenu;
And also since, your state contains only an array of orders, you can just return the updated array without having to override the previous state.
In your reducer:
case "ADD_ITEM_IN_ORDER":
const tempOrder1 = [...state.Order];
const index1 = state.Order.findIndex((item) => item.id === action.item.id);
if (index1 >= 0) {
console.log("before",tempOrder1[index1].Count);
tempOrder1[index1].Count += 1;
return { Order: [...tempOrder1] }; //Return updated array only
}
else {
console.log("New item added");
return {
Order: [...state.Order, action.item] //Return updated array only
};
}
default:
return state;
}

How to create infinite scroll in React and Redux?

import React, {useState, useEffect} from 'react';
import {connect} from 'react-redux';
import {
fetchRecipes
} from '../../store/actions';
import './BeerRecipes.css';
const BeerRecipes = ({recipesData, fetchRecipes}) => {
const [page, setPage] = useState(1);
const [recipes, setRecipes] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchRecipes();
}, [])
return (
<div className='beer_recipes_block'>
<div className='title_wrapper'>
<h2 className='title'>Beer recipes</h2>
</div>
<div className='beer_recipes'>
<ul className='beer_recipes_items'>
{
recipesData && recipesData.recipes && recipesData.recipes.map(recipe =>
<li className='beer_recipes_item' id={recipe.id}>{recipe.name}</li>
)
}
</ul>
</div>
</div>
);
};
const mapStateToProps = state => {
return {
recipesData: state.recipes
}
}
const mapDispatchToProps = dispatch => {
return {
fetchRecipes: () => dispatch(fetchRecipes())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(BeerRecipes);
this is my component where I would like to create infinite scroll and below is my redux-action with axios:
import axios from "axios";
import * as actionTypes from "./actionTypes";
export const fetchRecipesRequest = () => {
return {
type: actionTypes.FETCH_RECIPES_REQUEST
}
}
export const fetchRecipesSuccess = recipes => {
return {
type: actionTypes.FETCH_RECIPES_SUCCESS,
payload: recipes
}
}
export const fetchRecipesFailure = error => {
return {
type: actionTypes.FETCH_RECIPES_FAILURE,
payload: error
}
}
export const fetchRecipes = (page) => {
return (dispatch) => {
dispatch(fetchRecipesRequest)
axios
.get('https://api.punkapi.com/v2/beers?page=1')
.then(response => {
const recipes = response.data;
dispatch(fetchRecipesSuccess(recipes));
})
.catch(error => {
const errorMsg = error.message;
dispatch(fetchRecipesFailure(errorMsg));
})
}
}
I want to create a scroll. I need, firstly, to display first 10 elements and then to add 5 elements with every loading. I have 25 elements altogether and when the list is done it should start from the first five again.
Assuming you already have everything ready to load your next page. You can probably simplify the entire process by using a package like react-in-viewport so you don't have to deal with all the scroll listeners.
then you use it like this way.
import handleViewport from 'react-in-viewport';
const Block = (props: { inViewport: boolean }) => {
const { inViewport, forwardedRef } = props;
const color = inViewport ? '#217ac0' : '#ff9800';
const text = inViewport ? 'In viewport' : 'Not in viewport';
return (
<div className="viewport-block" ref={forwardedRef}>
<h3>{ text }</h3>
<div style={{ width: '400px', height: '300px', background: color }} />
</div>
);
};
const ViewportBlock = handleViewport(Block, /** options: {}, config: {} **/);
const Component = (props) => (
<div>
<div style={{ height: '100vh' }}>
<h2>Scroll down to make component in viewport</h2>
</div>
<ViewportBlock
onEnterViewport={() => console.log('This is the bottom of the content, lets dispatch to load more post ')}
onLeaveViewport={() => console.log('We can choose not to use this.')} />
</div>
))
What happen here is, it creates a 'div' which is outside the viewport, once it comes into the view port ( it means user already scrolled to the bottom ), you can call a function to load more post.
To Note: Remember to add some kind of throttle to your fetch function.

Cannot access ___ before initialization reactjs useState useTracker Subscriptions Form state meteor

I have a form that takes its state from a react useState hook, that hooks default value I would like to come from a useTracker call, I am using pub sub in Meteor to do this. I get a error Cannot access '' before initialization I know it has something to do with the lead not being ready yet and returning undefined and the hook not being able to use that, at least I think so. But I am not sure how to solve that.
Here is my code thus far
import React, { useState } from "react";
import Dasboard from "./Dashboard";
import { Container } from "../styles/Main";
import { LeadsCollection } from "../../api/LeadsCollection";
import { LeadWalkin } from "../leads/LeadWalkin";
import { useTracker } from "meteor/react-meteor-data";
const Walkin = ({ params }) => {
const [email, setEmail] = useState(leads.email);
const handleSubmit = (e) => {
e.preventDefault();
if (!email) return;
Meteor.call("leads.update", email, function (error, result) {
console.log(result);
console.log(error);
});
setEmail("");
};
const { leads, isLoading } = useTracker(() => {
const noDataAvailable = { leads: [] };
if (!Meteor.user()) {
return noDataAvailable;
}
const handler = Meteor.subscribe("leads");
if (!handler.ready()) {
return { ...noDataAvailable, isLoading: true };
}
const leads = LeadsCollection.findOne({ _id: params._id });
return { leads };
});
console.log(leads);
//console.log(params._id);
const deleteLead = ({ _id }) => {
Meteor.call("leads.remove", _id);
window.location.pathname = `/walkin`;
};
return (
<Container>
<Dasboard />
<main className="split">
<div>
<h1>Edit a lead below</h1>
</div>
{isLoading ? (
<div className="loading">loading...</div>
) : (
<>
<LeadWalkin
key={params._id}
lead={leads}
onDeleteClick={deleteLead}
/>
<form className="lead-form" onSubmit={handleSubmit}>
<input
type="text"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Type to edit lead"
/>
<button type="submit">Edit Lead</button>
</form>
</>
)}
</main>
</Container>
);
};
export default Walkin;
It should work if you change the order of these two hooks, but it's probably better to break this into two components so that you can wait until your subscription is ready before you try to use leads.email as default value. It's not possible to branch out ('return loading`) in between hooks, because React doesn't like it when the number of hooks it finds in a component change in-between re-renderings.
const Walkin = ({ params }) => {
const { leads, isLoading } = useTracker(() => {
const noDataAvailable = { leads: [] };
if (!Meteor.user()) {
return noDataAvailable;
}
const handler = Meteor.subscribe("leads");
if (!handler.ready()) {
return { ...noDataAvailable, isLoading: true };
}
const leads = LeadsCollection.findOne({ _id: params._id });
return { leads };
});
if (isLoading || !leads) {
return <div>loading..</div>;
} else {
return <SubWalkin params=params leads=leads />;
}
};
const SubWalkin = ({ params, leads }) => {
const [email, setEmail] = useState(leads.email);
...
};

How to add page number to the URL

Could someone please tell me how can I add page number to my url. The component is as follows:
/** NPM Packages */
import React, { Component } from "react";
import { connect } from "react-redux";
import { Spinner, Pagination } from "react-bootstrap";
//import styles from "./App.module.css";
/** Custom Packages */
import List from "../List";
//import fetchCategories from "../../../actions/configuration/category/fetchCategories";
import deleteCategory from "../../../actions/configuration/category/deleteCategory";
import API from "../../../../app/pages/utils/api";
class Category extends Component {
constructor(props) {
super(props);
this.state = {
mesg: "",
mesgType: "",
isLoading: true,
total: null,
per_page: null,
current_page: 1,
pdata: []
};
this.fetchCategoriesAPI = this.fetchCategoriesAPI.bind(this);
}
fetchCategoriesAPI = async pno => {
await API.get("categories?offset=" + (pno.index+1))
.then(res => this.setState({ pdata: res.data }))
.then(() => this.props.passToRedux(this.state.pdata))
.catch(err => console.log(err));
};
componentDidMount = async () => {
const { state } = this.props.location;
if (state && state.mesg) {
this.setState({
mesg: this.props.location.state.mesg,
mesgType: this.props.location.state.mesgType
});
const stateCopy = { ...state };
delete stateCopy.mesg;
this.props.history.replace({ state: stateCopy });
}
this.closeMesg();
await this.fetchCategoriesAPI(1);
this.setState({ isLoading: false });
};
onDelete = async id => {
this.props.removeCategory(id);
await deleteCategory(id).then(data =>
this.setState({ mesg: data.msg, mesgType: "success" })
);
this.closeMesg();
};
closeMesg = () =>
setTimeout(
function() {
this.setState({ mesg: "", mesgType: "" });
}.bind(this),
10000
);
/** Rendering the Template */
render() {
let activePage = this.state.pdata.currPage;
let items = [];
let totalPages = Math.ceil(this.state.pdata.totalCount / 10);
for (let number = 1; number <= totalPages; number++) {
items.push(
<Pagination.Item key={number} active={number == activePage}>
{number}
</Pagination.Item>
);
}
const paginationBasic = (
<div>
<Pagination>
{items.map((item,index)=>{
return <p key={index} onClick={() => this.fetchCategoriesAPI({index})}>{item}</p>
})}
</Pagination>
<br />
</div>
);
const { mesg, mesgType, isLoading } = this.state;
return (
<>
{mesg ? (
<div
className={"alert alert-" + mesgType + " text-white mb-3"}
role="alert"
>
{mesg}
</div>
) : (
""
)}
{isLoading ? (
<div className="container-fluid">
<h4
className="panel-body"
style={{ "text-align": "center", margin: "auto" }}
>
Loading
<Spinner animation="border" role="status" />
</h4>
</div>
) : (
<div>
<List
listData={this.props.categories}
listName="category"
_handleDelete={this.onDelete.bind(this)}
/>
{paginationBasic}
</div>
)}
</>
);
}
}
const matchStatestoProps = state => {
return { categories: state.categories };
};
const dispatchStatestoProps = dispatch => {
return {
passToRedux: pload =>
dispatch({ type: "FETCH_CATEGORIES", payload: pload }),
removeCategory: id => dispatch({ type: "DELETE_CATEGORY", payload: id })
};
};
export default connect(matchStatestoProps, dispatchStatestoProps)(Category);
the route is as follows:
<Route exact path="/categories/:page?" component={Category} />
So basically I want the page number to be displayed in the URL. Also if I change the page number, the data should load the corresponding page. Please help me
Could someone please help me out?
In a class component:
Your router will pass match in as a prop. When your component mounts, get this.props.match.params.page and load the data accordingly:
class MyComponent extends React.Component {
componentDidMount () {
// get the 'page' param out of the router props.
// default to 0 if not specified.
const { page = 0 } = this.props.match.params;
// it comes in as a string, parse to int
const p = parseInt(page, 10);
// do whatever you need to do (load data, etc.)
}
}
In a function component:
In a function component, you can get the page param via react-router's useParams hook:
import { useParams } from 'react-router-dom';
function MyComponent () {
const { page } = useParams(); // get the 'page' router param
const p = parseInt(page, 10); // comes in as a string, convert to int
// do whatever you need to do with it
}
If you need prev/next navigation you can deduce those page numbers from the current page.
I made this quick example that demonstrates how to access and use the route's url parameters via react router's useParams hook and how to do it via the match prop with a class component.
You can get page number from props like this:
const matchStatestoProps = (state, ownProps) => {
return { id: ownProps.match.params.id; categories: state.categories };
};
In your routes:
<Route path="/page/:id" component={Page} />

Resources