Duplicate image download issue in reactjs - reactjs

Two components are used to process the image operation. First component is ShowFormsList inside it onDownloadFile is used and another component ShowImgList which is used to pass onDownloadFile method and ImgList as props .
ShowFormsList.jsx
import React, { useEffect, useContext } from "react";
import PropTypes from "prop-types";
import {
Subheading,
Accordion,
AccordionItem,
} from "#contentful/forma-36-react-components";
import Jarvis from "../../utils/Jarvis";
import LoadingContext from "../../context/LoadingContext";
import ShowImgList from "./ShowImgList";
const ShowFormsList = ({ sdk }) => {
const [view, setView] = React.useState(null);
const jarvisSdk = new Jarvis(sdk);
const { showLoading, hideLoading } = useContext(LoadingContext);
const fieldType = (sdk && sdk.field.id) || "";
// const [update, setUpdate] = React.useState(false);
const autoScroll = () => {
sdk.window.startAutoResizer();
};
useEffect(() => {
sdk.window.startAutoResizer();
});
const getFormData = async () => {
showLoading();
const formData = await jarvisSdk.getFormData(sdk.ids.entry);
const formArr =
formData &&
formData.length > 0 &&
formData.map((form) => {
const formObj = {
formName: form.formName,
formId: form.id,
formDescription: form.description,
};
// const imgArr = [];
const imgArr =
form.formImages &&
form.formImages.length > 0 &&
form.formImages.map((img) => {
console.log(img);
const imgObj = {
imageName: img.imageName,
type: img.type,
extension: img.imageName.split(".")[1],
id: img.id,
};
// imgArr.push(imgObj);
return imgObj;
});
formObj.images = imgArr;
return formObj;
// formArr.push(formObj);
});
if (formArr && formArr.length > 0) {
setView(formArr);
}
hideLoading();
};
useEffect(async () => {
await getFormData();
}, []);
const onDownloadFile = async (id) => {
showLoading();
const downloadedFile = await jarvisSdk.downloadFormFile(id, fieldType);
const contentDispositionHeader = downloadedFile.responseObj.request
.getResponseHeader("Content-disposition")
.split("filename=");
const fileName =
contentDispositionHeader[contentDispositionHeader.length - 1];
const url = window.URL.createObjectURL(new Blob([downloadedFile.blob]));
const link = document.createElement("a");
link.setAttribute("download", `${fileName}`);
link.setAttribute("id", Math.random() + fileName);
link.href = url;
document.body.appendChild(link);
link.click();
link.remove();
hideLoading();
};
return (
<>
{view && view.length > 0 && (
<div className="App">
<div className="Card">
<div className="container">
<Accordion>
{view.map((item) => (
<AccordionItem
key={item.formId}
title={
<a
className="text-decoration"
href={`https://app.contentful.com/spaces/${sdk.ids.space}/environments/${sdk.ids.environment}/entries/${item.formId}`}
target="_black"
>
{item.formName}
</a>
}
onExpand={() => autoScroll()}
onCollapse={() => autoScroll()}
>
<ShowImgList
key={item.formId}
images={item.images}
onDownloadFile={onDownloadFile}
sdk={sdk}
/>
</AccordionItem>
))}
</Accordion>
</div>
</div>
</div>
)}
{!view && (
<div className="App">
<div className="Card">
<div className="container">
<div className="dropZoneContainer">
<div className="Dropzone">
<Subheading className="d-color">No data found.</Subheading>
</div>
</div>
</div>
</div>
</div>
)}
</>
);
};
const propTypes = {
sdk: PropTypes.instanceOf(Object),
};
ShowFormsList.defaultProps = {
sdk: undefined,
};
ShowFormsList.propTypes = propTypes;
export default ShowFormsList;
The second component is used to call the onDownloadFile method and perform the UI part operation.
ShowImgList.jsx
import {
Table,
TableBody,
TableCell,
TableHead,
TableRow,
TabPanel,
Icon,
} from "#contentful/forma-36-react-components";
import React, { useEffect } from "react";
import PropTypes from "prop-types";
const ShowImgList = (props) => {
const { images, onDownloadFile, sdk } = props;
useEffect(() => {
sdk.window.startAutoResizer();
});
const defaultTab = "first";
return (
<div className="fileVersionContainer">
<TabPanel id={defaultTab} className="versionContainer">
<div>
<Table>
<TableHead>
<TableRow>
<TableCell>Image</TableCell>
<TableCell>File Name</TableCell>
<TableCell>Type</TableCell>
<TableCell>Download</TableCell>
</TableRow>
</TableHead>
<TableBody>
{images &&
images.length > 0 &&
images.map((aImg) => (
<TableRow key={aImg.id}>
<TableCell key={`${aImg.id}cell`}>
<Icon key={`${aImg.id}asset`} icon="Asset" />
</TableCell>
<TableCell key={`-name-${aImg.id}`}>
{aImg.imageName}
</TableCell>
<TableCell key={`-type-${aImg.id}`}>
{aImg.extension}
</TableCell>
<TableCell key={`-download-${aImg.id}`}>
<Icon
icon="Download"
className="cursor-pointer"
onClick={() => onDownloadFile(aImg.id)}
/>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</TabPanel>
</div>
);
};
const propTypes = {
sdk: PropTypes.instanceOf(Object),
images: PropTypes.instanceOf(Array),
onDownloadFile: PropTypes.func,
};
ShowImgList.defaultProps = {
sdk: undefined,
images: undefined,
onDownloadFile: undefined,
};
ShowImgList.propTypes = propTypes;
export default ShowImgList;

Related

pagination in reactjs not working properly

I have a pagination component I am exporting to paginate set of products in another page but it doesn't seem to work well as there are 4 items in the database but the only number visible in the pagination list is just 1 and I can't figure out what's wrong
components/Pagination.jsx
import React from 'react'
const Pagination = ({postsPerPage, totalPosts, paginate}) => {
const pageNumbers = []
for(let i = 1; i <= Math.ceil(totalPosts / postsPerPage); i++){
pageNumbers.push(i)
}
return (
<div>
<div className="pagination page navigation">
<ul className="Pagination-list">
{pageNumbers.map(number =>
<li key={number} className="page-item">
<a onClick={() => paginate(number)} href="#" className="page-link">
{number}
</a>
</li>
)}
</ul>
</div>
</div>
)
}
export default Pagination
import React, { useEffect, useState } from 'react'
import {client} from '../lib/client'
import { motion } from 'framer-motion';
import { Product, FooterBanner, HeroBanner, Pagination } from '../components'
const Home = ({products, bannerData}) => {
// pagination
const [currentPage, setCurrentPage] = useState(1)
const [postsPerPage] = useState(1)
const indexOfLastPost = currentPage * postsPerPage
const indexofFirstPost = indexOfLastPost - postsPerPage
const currentPosts = products.slice(indexofFirstPost, indexOfLastPost)
const paginate = (pageNumber) => setCurrentPage(pageNumber)
// search filter
const [productItems, setProductItems] = useState([])
const [filterWork, setFilterWork] = useState([]);
const [activeFilter, setActiveFilter] = useState('All');
const [animateCard, setAnimateCard] = useState({ y: 0, opacity: 1 });
useEffect(() => {
setProductItems(currentPosts)
setFilterWork(currentPosts)
}, [])
const handleProductFilter = (item) => {
setActiveFilter(item)
setAnimateCard([{ y: 100, opacity: 0 }]);
setTimeout(() => {
setAnimateCard([{ y: 0, opacity: 1 }]);
if (item == 'All'){
setFilterWork(productItems)
}else{
setFilterWork(productItems.slice(0).reverse().map((productItem)=> productItem.tags.includes(item)))
}
}, 500)
}
return (
<>
<HeroBanner heroBanner={bannerData.length && bannerData[0]} />
<div className='products-heading'>
<h2>Best Selling Products</h2>
<p>Smoke accessories of many variations</p>
</div>
<div className='product_filter'>
{['Lighter', 'Pipe', 'Roller', 'Hookah', 'All'].map((item, index) => (
<div
key={index}
className={`product_filter-item app__flex p-text ${activeFilter === item ? 'item-active' : ''}`}
onClick={() => handleProductFilter(item)}
>
{item}
</div>
))}
</div>
<motion.div
animate={animateCard}
transition={{ duration: 0.5, delayChildren: 0.5 }}
>
<div className='products-container'>
{
filterWork.map((product) => <Product key={product._id} product={product} />)
}
</div>
<Pagination postsPerPage={postsPerPage} totalPosts={filterWork.length} paginate={paginate} />
</motion.div>
<FooterBanner footerBanner={bannerData && bannerData[0]} />
</>
)
};
export const getServerSideProps = async () => {
const query = '*[_type == "product"]'
const products = await client.fetch(query)
const bannerQuery = '*[_type == "banner"]'
const bannerData = await client.fetch(bannerQuery)
return {
props: {products, bannerData}
}
}
export default Home
The image below describes my explanation
I realized I wasn't passing in the products that was passed as props into the imported Pagination component to give access to all other Pagination features to work.
So I replaced the filterWork hook
<Pagination postsPerPage={postsPerPage} totalPosts={filterWork.length} paginate={paginate} />
With
<Pagination postsPerPage={postsPerPage} totalPosts={products.length} paginate={paginate} />
I also added currentPosts to data that should be filtered alongside filterWork
<div className='products-container'>
{
filterWork.map((product) => <Product key={product._id} product={product} />)
}
</div>
with
<div className='products-container'>
{
filterWork && currentPosts.map((product) => <Product key={product._id} product={product} />)
}
</div>

Pagination with reactjs

i'm working with React-App and backend/frontend API i get the response everything works fine , but i get too many responses ( over 100 ) at once, how can i go about only getting lets say ( 10 ) at a time, i've tried many things but they dont work. this is my code.
NOT ASKING FOR SOMEONE TO DO THE CODE FOR ME, BUT FOR A LITTLE HELP PUTTING ME ON THE RIGHT DIRECTION
REST API
import { createApi, fetchBaseQuery } from '#reduxjs/toolkit/query/react'
const baseUrl = 'http://localhost:3008';
const createRequest = (url) => ({ url });
export const playersAPI = createApi({
reducerPath: 'playersAPI',
baseQuery: fetchBaseQuery({ baseUrl }),
endpoints: (builder) => ({
getplayersAPI: builder.query({
query: (count) => createRequest(`/api/players?limit=${count}`),
}),
})
});
export const { useGetplayersAPIQuery } = playersAPI;
Front Page
import React, { useEffect, useState } from 'react';
import millify from 'millify';
import { Typography, Row, Col, Statistic } from 'antd';
import { Link } from 'react-router-dom';
import { Card } from 'antd';
import { useGetplayersAPIQuery } from '../services/playersAPI';
const { Title } = Typography;
const Players = ({ simplified }) => {
const count = simplified ? 10 : 100;
const { data: playersList, isFetching } = useGetplayersAPIQuery(count);
const [players, setPlayers] = useState();
const [searchTerm, setSearchTerm] = useState('');
console.log(players)
useEffect(() => {
setPlayers(playersList?.players);
const filteredData = playersList?.players.filter((name) => name.name.toLowerCase().includes(searchTerm));
setPlayers(filteredData);
}, [playersList, searchTerm]);
if (isFetching) return 'Loading...';
return (
<>
<div className="search-crypto">
<input placeholder="Search Players" onChange={(e) => setSearchTerm(e.target.value)} />
</div>
<Row gutter={[15, 15]} className="crypto-card-container">
{players?.map((name) => (
<Col xs={24} sm={12} lg={6} className="crypto-card" key={name.id}>
<Link to={`/players/${name.id}`}>
<Card
title={`${name.name}`}
hoverable
>
<p>Name: {(name.name)}</p>
<p>Status: {(name.status)}</p>
<p>Alliancce: {(name.alliance)}</p>
</Card>
</Link>
</Col>
))}
</Row>
</>
)
}
export default Players
This is my front page the squares the ones i want to show only 10 of them at a time, right now it shows all the data from the API.
[https://i.stack.imgur.com/UKHLi.jpg]
If you can't change your backend to add an extra parameter offset, you can do something like this.
const PlayersScreen = () => {
const [data, setData] = useState([]);
const [currentPage, setCurrentPage] = useState(1);
const [playersPerPage] = useState(10);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchPlayers = async () => {
const res = await fetch('https://jsonplaceholder.typicode.com/posts');
const data = await res.json();
setData(data);
setLoading(false)
};
fetchPlayers();
}, []);
// Get current players
const indexOfLastPlayer = currentPage * playersPerPage;
const indexOfFirstPlayer = indexOfLastPlayer - playersPerPage;
const currentPlayers = data.slice(indexOfFirstPlayer, indexOfLastPlayer);
// Change page
const paginate = pageNumber => setCurrentPage(pageNumber);
return (
<div className='container mt-5'>
<h1 className='text-primary mb-3'>My Players</h1>
<Players players={currentPlayers} loading={loading} />
<Pagination
playersPerPage={playersPerPage}
totalPlayers={data.length}
paginate={paginate}
/>
</div>
);
}
const Players = ({ players, loading }) => {
if (loading) {
return <h2>Loading...</h2>;
}
return (
<ul className='list-group mb-4'>
{players.map(player => (
<li key={player.id} className='list-group-item'>
{player.title}
</li>
))}
</ul>
);
}
const Pagination = ({ playersPerPage, totalPlayers, paginate }) => {
const pageNumbers = [];
for (let i = 1; i <= Math.ceil(totalPlayers / playersPerPage); i++) {
pageNumbers.push(i);
}
return (
<nav>
<ul className='pagination'>
{pageNumbers.map(number => (
<li key={number} className='page-item'>
<a onClick={() => paginate(number)} href='!#' className='page-link'>
{number}
</a>
</li>
))}
</ul>
</nav>
);
}

Issue closing Websocket thread

I have been working on refactoring code for a crypto trading platform - changing the class based components to functional components.
The main aim is to display every info of selected coin pair of binance.com using websocket.
If coin pair is changed by selecting option, we have to close old socket and create new socket connection, at first the plan was to implement this in TradeCoinSelection.tsx, but there is a part that is getting coin pair list and calling function of 'parent' (index.tsx) with changed info.
What is happening is the socket thread is not killed, even though there is close() function, the close() function is not finding the old socket thread because ws is resetting in re-rendering.
This means the front end seems to be alternating between new and old pair data as the old pair socket thread is not closed properly.
Any help is appreciated.
Functional component code - TradeCoinSection.tsx
import { useState, useEffect } from 'react';
import Select, { Option } from 'rc-select';
import { Row, Col, Card } from 'reactstrap';
import TradeApi from '#api/TradeApi';
import { IExchangeListAPIResponse } from '#api/types/exchange';
import { connect } from 'react-redux';
import { AppDispatch, RootState } from '#redux/store';
import { fetchExchangeCoinListAction } from '#redux/actions/Exchange';
import { ICoinSymbolResponse } from '#api/types/trade';
import ReconnectingWebSocket from 'reconnecting-websocket';
function TradeCoinSection (props: any) {
const [disabled] = useState<boolean>(false);
const [symbolLoader, setSymbolLoader] = useState<boolean>(false);
const [symbolList, setSymbolList] = useState<Array<ICoinSymbolResponse>>(props.exchangeCoinList);
const [binanceTickerData, setBinanceTickerData] = useState<Record<string, string>>({
h: "",
c: "",
b: "",
p: "",
l: "",
v: "",
q: "",
});
const [didLoadSymbolList] = useState<boolean>(false);
let ws : ReconnectingWebSocket = new ReconnectingWebSocket('wss://stream.binance.com:9443/ws/bnbusdt#ticker');
ws.close();
const closeAndOpenTickerStream = (symbol: string) => {
if(symbol != "") {
ws.close()
ws = new ReconnectingWebSocket('wss://stream.binance.com:9443/ws/'+symbol.toLowerCase()+'#ticker');
ws.addEventListener('open', () => {
// console.log('connected');
});
// eslint-disable-next-line #typescript-eslint/no-explicit-any
ws.onmessage = (evt: any) => {
// listen to data sent from the websocket server
const message = JSON.parse(evt.data);
setBinanceTickerData(message);
};
ws.addEventListener('close', () => {
// console.log('disconnected');
});
}
};
// eslint-disable-next-line #typescript-eslint/no-explicit-any
const onSymbolChangeHandler = (value: string, option: any) => {
let baseAssest = '';
let quoteAssest = '';
if (value) {
const splitArray = option.key.split('-');
if (splitArray.length > 1) {
baseAssest = splitArray[0];
quoteAssest = splitArray[1];
}
}
console.log("1,", value);
props.symbolChangeHandler(value, quoteAssest, baseAssest);
closeAndOpenTickerStream(value);
};
const fetchCoinPairList = async () => {
try {
setSymbolLoader(true);
// setErrorList([]);
console.log("extra-1")
if(props.selectedExchangeAccountSite != '') {
const response = await TradeApi.getCoinPair(props.selectedExchangeAccountSite);
// const response = await TradeApi.getCoinPair();
setSymbolList(response.data);
props.fetchExchangeCoinListAction(response.data);
let symbolObj = response.data[0];
if (props.selectedSymbol) {
response.data.find((obj) => {
if (obj.symbol === props.selectedSymbol) {
symbolObj = obj;
}
});
}
onSymbolChangeHandler(symbolObj.symbol, {
key: symbolObj.symbol_pair,
value: symbolObj.symbol,
});
}
// eslint-disable-next-line #typescript-eslint/no-explicit-any
} catch (err: any) {
} finally {
setSymbolLoader(false);
console.log(symbolLoader)
}
};
useEffect(() => {
if (props.exchangeCoinList && props.exchangeCoinList.length < 1) {
fetchCoinPairList();
}
return () => {
ws.close();
}
}, []);
useEffect(() => {
if(props.selectedExchangeAccountSite !== '' && !didLoadSymbolList) {
fetchCoinPairList();
}
if (
props.exchangeCoinList &&
props.exchangeCoinList.length > 0 &&
!props.selectedSymbol_state
) {
setSymbolList(props.exchangeCoinList);
let symbolObj = props.exchangeCoinList[0];
if (props.selectedSymbol) {
props.exchangeCoinList.find((obj: any) => {
if (obj.symbol === props.selectedSymbol) {
symbolObj = obj;
}
});
}
onSymbolChangeHandler(symbolObj.symbol, {
key: symbolObj.symbol_pair,
value: symbolObj.symbol,
});
}
}, [props.selectedExchangeAccount]);
const temp_disabled = disabled;
return (
<Card className="shadow-xl p-3">
<Row className="less-gutters align-items-start">
<Col xl={2} lg={12} md="12">
<Row className="less-gutters flex-column flex-sm-row flex-xl-column">
{!props.dropdownDisabled && (
<Col sm={6} md={6} lg={6} xl={12}>
<Select
placeholder="Select account"
style={{ width: '100%' }}
value={props.selectedExchangeAccount}
// eslint-disable-next-line #typescript-eslint/no-explicit-any
onChange={(exchangeId: number, options: any) =>
props.exchangeAccountChangeHandler(exchangeId, options.label, options.site)
}
className="mb-2"
>
{props.exchangeList.length > 0
? props.exchangeList.map((obj: IExchangeListAPIResponse) => (
<Option key={obj.id} value={obj.id} label={obj.account_name} site={obj.exchange}>
<b>{obj.account_name}</b>
</Option>
))
: null}
</Select>
</Col>
)}
<Col sm={6} md={6} lg={6} xl={12}>
<Select
showSearch={true}
disabled={
props.dropdownDisabled ? props.dropdownDisabled : temp_disabled
}
placeholder="Select Symbol"
style={{ width: '100%' }}
value={props.selectedSymbol}
onChange={onSymbolChangeHandler}
className="currency-dropdown"
>
{symbolList.length > 0
? symbolList.map((obj: ICoinSymbolResponse) => (
<Option value={obj.symbol} key={obj.symbol_pair}>
<b>{obj.symbol_pair}</b>
</Option>
))
: null}
</Select>
</Col>
</Row>
</Col>
<Col xl={10} lg={12} md="12">
<div className="d-flex w-100 market-data-container mt-md-3 mt-lg-3 mt-xl-0">
<div className="w-100 d-flex align-items-start mt-3 mt-md-0 sm:flex-col">
<div className="last-price-block col-md-4 col-lg-3 pl-0 pl-md-0 pl-lg-3 pr-3">
<div className="market-data-last-price">
{binanceTickerData.h &&
parseFloat(binanceTickerData.c).toFixed(8)}
</div>
<div className="market-data-worth">
{binanceTickerData.b &&
`${parseFloat(binanceTickerData.b).toFixed(8)}`}
</div>
</div>
<div className="market-data d-flex flex-wrap w-100 justify-content-start pl-0 pl-md-3 mt-3 mt-md-0">
<div className="market-data-block w-33 mb-md-3 mb-lg-2">
<div className="market-data-block-title">24h Change</div>
<div className="market-data-block-value">
{binanceTickerData.p &&
parseFloat(binanceTickerData.p).toFixed(2)}{' '}
{binanceTickerData.p}%
</div>
</div>
<div className="market-data-block w-33 mb-md-3 mb-lg-2">
<div className="market-data-block-title">24h High</div>
<div className="market-data-block-value">
{binanceTickerData.h &&
parseFloat(binanceTickerData.h).toFixed(8)}
</div>
</div>
<div className="market-data-block w-33 mb-md-3 mb-lg-2">
<div className="market-data-block-title">24h Low</div>
<div className="market-data-block-value">
{binanceTickerData.l &&
parseFloat(binanceTickerData.l).toFixed(8)}
</div>
</div>
<div className="market-data-block w-33 mb-md-3 mb-lg-0">
<div className="market-data-block-title">
24h Volume({props.quoteAssest})
</div>
<div className="market-data-block-value">
{binanceTickerData.v &&
parseFloat(binanceTickerData.v).toFixed(2)}
</div>
</div>
<div className="market-data-block w-33 mb-md-0 mb-lg-0">
<div className="market-data-block-title">
24h Volume({props.baseAssest})
</div>
<div className="market-data-block-value">
{binanceTickerData.q &&
parseFloat(binanceTickerData.q).toFixed(2)}
</div>
</div>
</div>
</div>
</div>
</Col>
</Row>
</Card>
);
}
const mapStateToProps = (state: RootState, props: any) => ({
exchangeCoinList: state.exchange.exchangeCoinList,
ownProps: props
});
const mapDispatchToProps = (dispatch: AppDispatch) => ({
fetchExchangeCoinListAction: (data: Array<ICoinSymbolResponse>) =>
dispatch(fetchExchangeCoinListAction(data)),
});
// export default TradeCoinSection;
export default connect(mapStateToProps, mapDispatchToProps)(TradeCoinSection);
Parent (Index.tsx)
import { useState, useEffect } from 'react';
import { withRouter } from 'react-router-dom';
import { Row, Col } from 'reactstrap';
import { BarChart2 } from 'react-feather';
import { connect } from 'react-redux';
// Import Api and constants;
import TradeApi from '#api/TradeApi';
import AppConfig from '#config/config';
import { getCatchError } from '#helpers/ErrorHandler';
import { RootState } from '#redux/store';
import TradeCoinSection from '#pages/Admin/Trade/Trade/partials/TradeCoinSection';
import TradeChartSection from '#pages/Admin/Trade/Trade/partials/TradeChartSection';
import OrderHistorySection from '#pages/Admin/Trade/OrderHistory/partials/OrderHistorySection';
import OpenOrderSection from '#pages/Admin/Trade/OpenOrder/partials/OpenOrderSection';
import PageTitle from '#components/ui/PageTitle/PageTitle';
import TradeAddSection from '#pages/Admin/Trade/Trade/partials/TradeAddSection';
import EmptyDataComponent from '#components/EmptyDataComponent';
import LoaderComponent from '#components/LoaderComponent';
import ExchangeModal from '../../Settings/partials/ExchangeModal';
function TradePage (props: any) {
const [ symbolPriceObj, setSymbolPriceObj ] = useState({
symbol: '',
price: '',
status: '',
filter_data: null,
symbol_info: null,
} as any);
const [symbolDetailLoader, setSymbolDetailLoader] = useState<boolean>(false);
const [isAddExchangeModalOpen, setIsAddExchangeModalOpen] = useState<boolean>(false);
const [selectedExchangeAccount, setSelectedExchangeAccount] = useState<number>(0);
const [selectedExchangeAccountLabel, setSelectedExchangeAccountLabel] = useState<string>('');
const [selectedExchangeAccountSite, setSelectedExchangeAccountSite] = useState<string>('');
const [selectedSymbol, setSelectedSymbol] = useState<string>('');
const [baseAssest, setBaseAssest] = useState<string>('');
const [quoteAssest, setQuoteAssest] = useState<string>('');
const [errorList, setErrorList] = useState<Array<string>>([]);
const [lastRecordInsertedAt, setLastRecordInsertedAt] = useState<number>(0);
const postOrderHandler = () => {
setLastRecordInsertedAt(Date.now());
}
const addExchangeModalHandler = (isAdded?: boolean) => {
setIsAddExchangeModalOpen(!isAddExchangeModalOpen);
if (isAdded) {
setTimeout(() => {
window.location.reload();
}, 2000);
}
}
const exchangeAccountChangeHandler = (exchangeId: number, exchangeLabel: string, exchangeSite: string) => {
setSelectedExchangeAccount(exchangeId);
setSelectedExchangeAccountLabel(exchangeLabel);
setSelectedExchangeAccountSite(exchangeSite);
}
const symbolChangeHandler = (symbol: string, quoteAssest: string, baseAssest: string) => {
setSelectedSymbol(symbol);
setQuoteAssest(quoteAssest);
setBaseAssest(baseAssest);
}
const fetchSymbolDetailHandler = async () => {
try {
setSymbolDetailLoader(true);
setErrorList([]);
const response = await TradeApi.getCoinPairPrice(selectedExchangeAccount, selectedSymbol);
const coinObj = {
filter_data: response.data.filter_data,
symbol_info: response.data.symbol_info,
price: response.data.price,
symbol: '',
status: '',
};
setSymbolPriceObj(coinObj);
} catch (error: any) {
console.log(symbolDetailLoader, errorList);
setSymbolDetailLoader(false);
setErrorList(getCatchError(error));
} finally {
setSymbolDetailLoader(false);
}
}
useEffect(() => {
if(selectedSymbol != '')
fetchSymbolDetailHandler();
}, [selectedSymbol])
useEffect(() => {
document.title = `${AppConfig.REACT_APP_NAME} | Trade`;
if (props.exchangeList && props.exchangeList.length > 0) {
const exchangeList = props.exchangeList;
setSelectedExchangeAccount(exchangeList[0].id);
setSelectedExchangeAccountLabel(exchangeList[0].account_name);
setSelectedExchangeAccountSite(exchangeList[0].exchange);
postOrderHandler();
}
}, []);
useEffect(() => {
if (props.exchangeList.length > 0) {
const exchangeList = props.exchangeList;
setSelectedExchangeAccount(exchangeList[0].id);
setSelectedExchangeAccountLabel(exchangeList[0].account_name);
setSelectedExchangeAccountSite(exchangeList[0].exchange);
postOrderHandler();
}
}, [props]);
useEffect(() => {
postOrderHandler();
}, [selectedExchangeAccount])
return (
<>
<PageTitle
titleHeading="Trade"
titleDescription="Buy and sell all of your currencies"
pageTitleIconBox={true}
pageTitleIcon={<BarChart2 className="display-2 text-primary" />}
/>
{props.exchangeLoader && <LoaderComponent isSmallLoader={true} />}
{!props.exchangeLoader &&
props.exchangeList &&
props.exchangeList.length > 0 && (
<>
<Row className="less-gutters buysell">
<Col xl={3} lg={4} md={5} className="mb-4 mb-md-0 pl-2 pr-2">
<TradeAddSection
selectedSymbol={selectedSymbol}
selectedExchangeAccount={selectedExchangeAccount}
selectedExchangeSite={selectedExchangeAccountSite}
symbolPriceObj={symbolPriceObj}
quoteAssest={quoteAssest}
baseAssest={baseAssest}
selectedAccountLabel={selectedExchangeAccountLabel}
postOrderHandler={postOrderHandler}
/>
</Col>
<Col xl={9} lg={8} md={7}>
<TradeCoinSection
selectedSymbol={selectedSymbol}
exchangeList={props.exchangeList}
selectedExchangeAccount={selectedExchangeAccount}
selectedExchangeAccountSite={selectedExchangeAccountSite}
exchangeAccountChangeHandler={exchangeAccountChangeHandler}
symbolChangeHandler={symbolChangeHandler}
quoteAssest={quoteAssest}
baseAssest={baseAssest}
/>
{selectedSymbol && (
<TradeChartSection selectedSymbol={selectedSymbol} />
)}
</Col>
</Row>
{selectedExchangeAccount && (
<OpenOrderSection
selectedExchangeAccount={selectedExchangeAccount}
lastRecordInsertedAt={lastRecordInsertedAt}
selectedSymbol={selectedSymbol}
isTradePage={true}
/>
)}
{selectedExchangeAccount && (
<OrderHistorySection
selectedExchangeAccount={selectedExchangeAccount}
lastRecordInsertedAt={lastRecordInsertedAt}
selectedSymbol={selectedSymbol}
isTradePage={true}
/>
)}
</>
)}
{!props.exchangeLoader &&
props.exchangeList &&
props.exchangeList.length < 1 && (
<Row className="less-gutters buysell">
<Col xl={12} lg={12} md={12}>
<EmptyDataComponent
message="No exchange connected. Please connect an exchange first"
buttonText="Connect Exchange"
buttonClickHandler={addExchangeModalHandler}
showExchangeLinkSection={true}
/>
<ExchangeModal
isOpen={isAddExchangeModalOpen}
toggler={addExchangeModalHandler}
/>
</Col>
</Row>
)}
</>
);
}
const mapStateToProps = (state: RootState, props: any) => ({
exchangeList: state.exchange.exchangeList,
exchangeLoader: state.exchange.exchangeListStatusObj.loading,
ownProps: props
});
export default withRouter(connect(mapStateToProps, {})(TradePage));

Search bar, <input/>

Hello everyone :D I need your advise/tip. Right now I have a APIDataTable component. It has its rows, columns and etc. This component is responsible to show/build data table on frontend with search bar in it above the table. I have an search bar, which is not functional right now. I want it to search data from data table. What should I start from? How can i make it perform search in Table. Thank you for any advise and tip <3
import React, { useEffect, useState } from "react";
import { plainToClassFromExist } from "class-transformer";
import { Pagination } from "../../models/Pagination";
import {
DataTable,
DataTableHead,
DataTableHeadCell,
DataTableBody,
DataTableRow,
DataTableCell,
} from "../DataTable";
import { request } from "../../api";
import "./index.css";
import { MenuSurface } from "../MenuSurface";
import { IconButton } from "../IconButton";
import { Checkbox } from "../Checkbox";
import { Dialog } from "../Dialog";
import { GridCell, GridRow } from "../Grid";
import { Button } from "../Button";
export class Column<T> {
label: string;
width?: number;
filter?: JSX.Element;
render: (row: T) => JSX.Element | string;
constructor(column: Partial<Column<T>>) {
Object.assign(this, column);
}
}
type APIDataTableProps<T> = {
apiPath?: string;
params?: string;
columns?: Column<T>[];
type: Function;
onRowClick?: (row: T) => void;
};
export const APIDataTable = <T extends object>({
apiPath,
params,
columns,
type,
onRowClick,
}: APIDataTableProps<T>) => {
const [data, setData] = useState<Pagination<T>>(null);
const [page, setPage] = useState(1);
const [pageSize, setPageSize] = useState(15);
const [isLoading, setIsLoading] = useState(false);
const [isDialogOpen, setDialogOpen] = useState(false);
const [isMenuSurFaceOpen, setMenuSurfaceOpen] = useState(false);
const [hiddenColumns, setHiddenColumns] = useState<number[]>(
JSON.parse(localStorage.getItem(`hiddenColumns-${apiPath + params}`)) || []
);
const fetchData = async () => {
const urlSearchParams = new URLSearchParams(params);
urlSearchParams.set("page", page.toString());
urlSearchParams.set("page_size", pageSize.toString());
const url = `${apiPath}?${urlSearchParams}`;
const response = await request(url);
const data = plainToClassFromExist(new Pagination<T>(type), response, {
excludeExtraneousValues: true,
});
setData(data);
setIsLoading(false);
};
useEffect(() => {
if (!!apiPath) {
setIsLoading(true);
fetchData();
}
}, [page, pageSize]);
const headCells = columns
.filter((e, i) => !hiddenColumns?.includes(i))
.map((column) => (
<DataTableHeadCell key={column.label} width={column.width}>
{column.label}
</DataTableHeadCell>
));
const rows = data?.results?.map((row, index) => (
<DataTableRow
key={"row-" + index}
onClick={() => !!onRowClick && onRowClick(row)}
>
{columns
.filter((e, i) => !hiddenColumns?.includes(i))
.map((column) => {
return (
<DataTableCell key={column.label} width={column.width}>
<div className="data-table-cell-text">{column.render(row)}</div>
</DataTableCell>
);
})}
</DataTableRow>
));
let uncheckedCheckboxes = hiddenColumns;
const onCheckboxChange = (index: number, value: boolean) => {
if (!value) {
uncheckedCheckboxes.push(index);
//setHiddenColumns(uncheckedCheckboxes);
} else {
const array = [...uncheckedCheckboxes];
array.splice(array.indexOf(index), 1);
uncheckedCheckboxes = array;
}
};
const [isOpen, setIsOpen] = useState(false);
return (
<div className="data-table-container">
<div className="search-test">
<div className="mdc-menu-surface--anchor">
<label
className="mdc-text-field mdc-text-field--filled mdc-text-field--no-label mdc-text-field--with-leading-icon mdc-text-field--with-trailing-icon"
htmlFor="input"
id="search-menu-surface"
>
<IconButton density={-1} icon="search" />
<input
className="mdc-text-field__input "
type="text"
placeholder="Поиск"
id="searchinput"
/>
<IconButton
density={-1}
icon="arrow_drop_down"
onClick={() => {
setMenuSurfaceOpen(true);
}}
/>
</label>
<MenuSurface
isOpen={isMenuSurFaceOpen}
onClose={() => setMenuSurfaceOpen(false)}
fullwidth
>
<div className="data-table-filters-container">
{columns.map(
(column) =>
!!column.filter && (
<div className="data-table-filter">
<div className="data-table-filter-label mdc-typography--subtitle1">
{column.label}
</div>
<div className="data-table-column-filter">
{column.filter}
</div>
</div>
// <GridRow>
// <GridCell span={3}>{column.label}</GridCell>
// <GridCell span={3}>{column.filter}</GridCell>
// </GridRow>
)
)}
{/* <GridCell span={2}> */}
{/* <Button label="Поиск" raised /> */}
{/* <Button
label="Отмена"
raised
onClick={() => {
setIsOpen(false);
}}
/> */}
{/* </GridCell> */}
</div>
</MenuSurface>
</div>
<IconButton
onClick={() => {
setDialogOpen(true);
}}
density={-1}
icon="settings"
/>
<Dialog
isOpen={isDialogOpen}
onOkClick={() => {
localStorage.setItem(
`hiddenColumns-${apiPath + params}`,
JSON.stringify(uncheckedCheckboxes)
);
setDialogOpen(false);
setHiddenColumns(uncheckedCheckboxes);
}}
onCloseClick={() => setDialogOpen(false)}
>
<div
style={{
display: "flex",
flexDirection: "column",
}}
>
{columns.map((column, index) => (
<Checkbox
label={column.label}
onChange={(value) => onCheckboxChange(index, value)}
defaultChecked={!uncheckedCheckboxes.includes(index)}
/>
))}
</div>
</Dialog>
</div>
<DataTable
pagination={true}
count={data?.count}
rowsNumber={data?.results.length}
page={page}
next={data?.next}
previous={data?.previous}
isLoading={isLoading}
onNextClick={() => setPage(page + 1)}
onPreviosClick={() => setPage(page - 1)}
>
<DataTableHead>{headCells}</DataTableHead>
<DataTableBody>{rows}</DataTableBody>
</DataTable>
</div>
);
};
I'm guessing that you want to search bar to effectively filter out rows that don't match. in this case what you want to do is add a filter to the search text (naturally you'll add a state for the search value, but it looks like you'll have that handled.
You'll add your filter here const rows = data?.results?.filter(...).map
You filter function will look something like this
const rows = data?.results.filter((row) => {
// In my own code if I have other filters I just make them return false
// if they don't match
if (
searchText &&
!(
// exact match example
row.field === searchText ||
// case-insensitive example
row.otherField?.toLowerCase().includes(searchText)
// can continue with '||' and matching any other field you want to search by
)
)
return false;
return true;
}).map(...)

How to passing functions to components?

I am doing the implementation of list pagination through a custom hook. The handleSetCurrentPage() function gets the correct number, it uses setCurrentPage(number). Consolelog setCurrentPage(number) showed undefined.
if you do all the same code only within one file (put everything in ListOfItems) it works fine.
Hook:
export const usePagination = (users = [], defaultPage = 1, amountPerPage = 10) => {
const [currentPage, setCurrentPage] = useState(defaultPage);
const [currentUsers, setCurrentUsers] = useState([]);
const [amountOfPages, setAmountOfPages] = useState(0);
useEffect(() => {
updateUsers();
updateAmountOfPages();
}, []);
const updateUsers = () => {
const indexOfLastPost = currentPage * amountPerPage;
const indexOfFirstPost = indexOfLastPost - amountPerPage;
const updatedUsers = users.slice(indexOfFirstPost, indexOfLastPost);
setCurrentUsers(updatedUsers);
};
const updateAmountOfPages = () => {
const updatedAmount = Math.ceil(users.length / amountPerPage);
setAmountOfPages(updatedAmount);
};
return {
setCurrentPage,
amountOfPages,
currentUsers,
};
};
list of items:
export function ListOfItems() {
const users = useSelector(state => state);
const { setCurrentPage, currentUsers, amountOfPages } = usePagination(users);
let {url} = useRouteMatch();
let items = currentUsers.map(function (value, index) {
return (
<form key={index}>
<div className="input-group">
<div className="input-group-prepend">
<Link className="input-group-text" to={`${url}/${index}`}>
{value.name}, {index}
</Link>
</div>
</div>
</form>
)
});
return (
<div>
{/*<form className="card">*/}
{/* <Search setSearch={setSearch} />*/}
{/*</form>*/}
<div>{items}</div>
<div>
<Pagination amountOfPages={amountOfPages} setCurrentPage={setCurrentPage}/>
</div>
</div>
)
}
pagination component:
const Pagination = ({amountOfPages, setCurrentPage}) => {
const [pageNumbers, setPageNumbers] = useState([]);
useEffect(() => {
calculatePageNumbers();
}, [amountOfPages]);
function calculatePageNumbers() {
const updatedPageNumbers = [];
for (let i = 1; i <= amountOfPages; i++) {
updatedPageNumbers.push(i);
}
setPageNumbers(updatedPageNumbers);
}
function handleSetCurrentPage(number) {
console.log(number);
return console.log(setCurrentPage(number));
}
return (
<nav>
<ul className="pagination">
{pageNumbers.map(number => (
<li key={number} className="page-item">
<button
onClick={() => handleSetCurrentPage(number)}
type="button"
className="page-link"
>
{number}
</button>
</li>
))}
</ul>
</nav>
);
};
export default Pagination;
useEffect(() => {
updateUsers();
updateAmountOfPages();
}, [currentPage]);

Resources