I am trying to build a search and sorting functionality for the table content. I don't want to use package as I am trying to learn and see how the react search work. I have the following that loads the content from payloads
import React, {useState, useEffect} from 'react'
import '../css/about.css';
import Pagination from '../components/Pagination'
function About() {
const [userData, setUserData] = useState([]);
const [loading , setLoading] = useState(false);
const [currentPage, setCurrentPage] = useState(1);
const [postsPerPage, setPostsPerPage] = useState(5);
const [search, setSearch] = useState("");
async function getData()
{
let response = await fetch('https://api.github.com/users');
let data = await response.json();
// setUserData(data)
return data;
}
//call getData function
getData()
.then(data => console.log(data)
);//
useEffect(() => {
setLoading(true)
getData()
.then(
data => {
setUserData(data) }
)
.catch(error => {
console.log(error);
})
}, [])
// Get current posts
const indexOfLastPost = currentPage * postsPerPage;
const indexOfFirstPost = indexOfLastPost - postsPerPage;
const currentPosts = userData.slice(indexOfFirstPost, indexOfLastPost);
// changw page
const paginate = (pageNumber) => setCurrentPage(pageNumber);
// Search Table
const handleFilterChange = e => {
const value = e.target.value || undefined;
if( search !== "" && userData.login.indexOf(search.toLowerCase()) === -1 ) {
return null;
}
setSearch(value)
}
return (
<div className="container">
<div>
<input value={search}
onChange={handleFilterChange}
placeholder={"Search"}
/>
<table>
<thead>
<tr>
<td>id</td>
<td>avatar_url</td>
<td>events_url</td>
<td>followers_url</td>
<td>following_url</td>
<td>gists_url</td>
<td>gravatar_id</td>
<td>html_url</td>
<td>login</td>
<td>node_id</td>
<td>organizations_url</td>
<td>received_events_url</td>
<td>repos_url</td>
<td>site_admin</td>
<td>starred_url</td>
<td>subscriptions_url</td>
<td>type</td>
<td>url</td>
</tr>
</thead>
<tbody>
{
currentPosts.map((item, index) => (
<tr key={index}>
<td>{item.id}</td>
<td>{item.avatar_url}</td>
<td>{item.events_url}</td>
<td>{item.followers_url}</td>
<td>{item.following_url}</td>
<td>{item.gists_url}</td>
<td>{item.gravatar_id}</td>
<td>{item.html_url}</td>
<td>{item.login}</td>
<td>{item.node_id}</td>
<td>{item.organizations_url}</td>
<td>{item.received_events_url}</td>
<td>{item.repos_url}</td>
<td>{item.site_admin}</td>
<td>{item.starred_url}</td>
<td>{item.subscriptions_url}</td>
<td>{item.type}</td>
<td>{item.url}</td>
</tr>
))
}
</tbody>
</table>
<Pagination postsPerPage={postsPerPage} totalPosts={userData.length} paginate={paginate} />
</div>
</div>
)
}
export default About
The pagination code is listed below.
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>
<ul className="pagination">
{pageNumbers.map(number => (
<li key={number} className="page-item">
<a onClick={() => paginate(number)}
href="#" className="page-link">
{number}
</a>
</li>
))}
</ul>
</div>
)
}
export default Pagination
I am think because I used .map within the tbody and the search isn't affecting the content. Though I have no error, only that nothing is displaying from search parameters.
I noticed you didn't create the function to handle the searching. You can use this generic approach which will search across the rows and the column and will match the cases.
function DataSearch(rows) {
const columns = rows[0] && Object.keys(rows[0]);
return rows.filter((row) =>
columns.some((column) => row[column].toString().toLowerCase().indexOf(search.toLowerCase()) > -1)
);
}
instantiate the function
const searchPosts = DataSearch(currentPosts);
Use the searchPosts on your .map function in tbody.
Related
I want my items could be add or removed.
Add items is ok, but remove one item will cause rest of items removed either.
I have tried the plain Object Array could be add/remove by these two functions, but the react element array can not work the same.
Here is I referenced the Delete function.
Here is my problem screen shot.
Here is code sandbox.
Anyone have suggestions?
Here is code:
import React, { useState, useRef, useCallback, useImperativeHandle } from 'react';
export default function List() {
const [itemList, setItemList] = useState([]);
const itemListRef = useRef([]);
const onDeleteProduct = useCallback(index => {
console.log("delete " + index);
setItemList(
itemList.filter((item, i) => i !== index)
);
})
function addSelectProduct() {
let index = itemList.length;
let newList = [];
newList.push(
<Item ref={r => itemListRef.current[index] = r}
number={index + 1} onDeleteProduct={() => onDeleteProduct(index)}
/>
);
setItemList(prevState => [...prevState,
...newList
]);
}
function allData() {
let len = itemListRef.current.lenth;
for(let i = 0; i < len; i++) {
console.log(itemListRef.current[i].getData());
}
}
return (
<>
<button onClick={addSelectProduct}>Add One</button>
<button onClick={allData}>show All data</button>
<table>
<thead>
<tr>
<th># No. </th>
<th>text </th>
<th>Operate</th>
</tr>
</thead>
<tbody>
{itemList}
</tbody>
</table>
</>
);
}
const Item = React.forwardRef(({ number, onDeleteProduct }, ref) => {
const [data, setData] = useState("");
useImperativeHandle(ref, () => ({
getData,
}));
const getData = () => {
return data;
}
return (
<tr key={number}>
<td>{number}</td>
<td><input value={data} onChange={e => setData(e.target.value)}/></td>
<td>
<button size="sm" variant="danger" onClick={() => onDeleteProduct(number)}>Delete</button>
</td>
</tr>
);
});
Looks like you don't have the last version of itemList. Maybe you should add itemList to the dependencies array of useCallback:
const onDeleteProduct = useCallback(index => {
console.log("delete " + index);
setItemList(
itemList.filter((item, i) => i !== index)
);
}, [itemList])
Furthermore, you should use a useCallback in addSelectProduct function too with its dependencies.
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>
);
}
I have created a crud operation page. When I am searching for any data from page 1 then the search functionality works very well but when I am going to another page using pagination button and after that when I am going to search something then it will returns me a empty result. can anyone figure it out , what i am doing wrong or where I can make changes?
import Wrapper from '../Helpers/Wrapper';
import './Users.css';
import { useEffect, useRef, useState } from "react";
import Modal from "../Layout/Modal";
import Button from '../UI/Button';
import axios from "axios";
import ReactPaginate from 'react-paginate';
const Users = () => {
const searchRef = useRef();
const [users, setUsers] = useState([]);
const [editUser, setEditUser] = useState([]);
const [openModal, setOpenModal] = useState(false);
const [searchValue, setSearchValue] = useState();
const [title, setTitle] = useState("Add a new User");
const [offset, setOffset] = useState(0);
const [perPage, setPerPage] = useState(10);
const [pageCount, setPageCount] = useState(0);
const [apid, setApid] = useState(null);
// The ids of users who are removed from the list
const [ids, setIds] = useState([]);
const [isCheckAll, setIsCheckAll] = useState(false);
const getApiData = async () => {
const res = await axios.get('https://geektrust.s3-ap-southeast-1.amazonaws.com/adminui-problem/members.json');
const data = res.data;
setApid(data);
const postData = data.slice(offset, offset + perPage);
setUsers(postData);
setPageCount(Math.ceil(data.length / perPage))
}
useEffect(() => {
getApiData();
}, [offset]);
const deleteRecord = userId => {
const newUsersList = [...users];
const index = users.findIndex(user => user.id === userId);
newUsersList.splice(index, 1);
setUsers(newUsersList);
};
const errorHandler = () => {
setOpenModal(false);
};
const addUsermodal = () => {
setOpenModal(true);
};
const onCheckHandler = userRecord => {
const filtereduser = users.findIndex(record => record.id === userRecord.id);
if (filtereduser != -1) {
users[filtereduser] = userRecord;
} else {
const newUsersList = [userRecord, ...users];
setUsers(newUsersList);
}
}
const editRecord = (record) => {
setEditUser(record);
setOpenModal(true);
setTitle('Update User');
};
const searchList = () => {
const enteredSearchData = searchRef.current.value;
setSearchValue(enteredSearchData);
};
const handlePageClick = async (e) => {
const selectedPage = (e.selected * 10) % apid.length;
setOffset(selectedPage);
};
const selectUserRecord = event => {
const selectedId = event.target.value;
if (ids.includes(selectedId)) {
const newIds = ids.filter((id) => id !== selectedId);
setIds(newIds);
} else {
const newIds = [...ids];
newIds.push(selectedId);
setIds(newIds);
}
};
const removeSelected = event => {
const remainingUser = users.filter(
user => !ids.includes(user.id)
)
setUsers(remainingUser);
};
const selectAllVisibleUserRecord = event => {
setIsCheckAll(!isCheckAll);
const postData = users.slice(offset, offset + perPage);
setIds(postData.map(li => li.id));
getApiData();
if (isCheckAll) {
setIds([]);
}
};
const btnMT = {
marginTop: '20px'
}
return (
<Wrapper>
{openModal && (<Modal
title={title}
editUser={editUser}
onConfirm={errorHandler}
oncheck={onCheckHandler}
/>)}
<div className="panelWrapper">
<h1>Users</h1>
<div className="addUsers" onClick={addUsermodal}>
<img src="./plus.svg" alt="add" />
<span>Add new User</span>
</div>
<div className="searchData">
<div className="searchBox">
<label htmlFor="search">Search</label>
<input
id="search"
type="text"
ref={searchRef}
/>
</div>
<Button type={'button'} onClick={searchList}>Search</Button>
</div>
<div className="usersList">
<div className="tableWrapper">
<table>
<thead>
<tr>
<th>
<input
type="checkbox"
name="selectAll"
id="selectAll"
onChange={selectAllVisibleUserRecord}
/>
</th>
<th>ID</th>
<th>Name</th>
<th>Email</th>
<th>Role</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{users.filter(data => {
if (searchValue === '' || searchValue === undefined) {
return data;
} else if (data.name.toLowerCase().includes(searchValue.toLowerCase())) {
return data;
} else if (data.email.toLowerCase().includes(searchValue.toLowerCase())) {
return data;
} else if (data.role.toLowerCase().includes(searchValue.toLowerCase())) {
return data;
}
}).map((listdata) => (
<tr key={listdata.id} className={ids.includes(listdata.id) ? 'checked' : ''}>
<td>
<input
id={`custom-checkbox-${listdata.id}`}
type="checkbox"
value={listdata.id}
name={listdata.name}
onChange={selectUserRecord}
checked={ids.includes(listdata.id) ? true : false}
/>
</td>
<td>{listdata.id}</td>
<td>{listdata.name}</td>
<td>{listdata.email}</td>
<td>{listdata.role}</td>
<td>
<img src="./edit.svg" alt="edit" onClick={event => editRecord(listdata)} />
<img src="./delete.svg" alt="Delete" onClick={event => deleteRecord(listdata.id)} />
</td>
</tr>
))}
</tbody>
</table>
</div>
<div style={btnMT}>
<Button type={'button'} onClick={removeSelected}>Delete Selected</Button>
</div>
<ReactPaginate
previousLabel={"prev"}
nextLabel={"next"}
breakLabel={"..."}
breakClassName={"break-me"}
pageCount={pageCount}
pageRangeDisplayed={5}
onPageChange={handlePageClick}
containerClassName={"pagination"}
subContainerClassName={"pages pagination"}
activeClassName={"active"} />
</div>
</div>
</Wrapper>
);
}
export default Users;
I got the State Data from Store. I created the Search Box to filter that Data, Now I got the FilterData also, But how I need to update my UI with that Filtered Data, In HandleSearch method I stored the the Filtered data in FilteredData varibale, But I am Unable to Iterate the FilteredData varibale and I am unable to update in the UI, BUt it is working in console, Now i need to update in the UI, Please can anyone help in this, Thanks in Advance...
import { Dispatch } from "redux"
import axios from 'axios'
export const FETCH_SUCCESS : string ='FETCH_SUCCESS';
export const FETCH_SEARCH ='FETCH_SEARCH';
export const fetchUser=()=>{
return async (dispatch:Dispatch) =>{
try{
let dataUrl : string ="http://localhost:3000/users";
let response = await axios.get(dataUrl);
dispatch({type:FETCH_SUCCESS, payload : response.data})
} catch {
}
}
}
import * as searchAction from './SearchAction';
import {SearchingInter} from '../../componets/SearchingInter';
export interface ISearch{
search : SearchingInter[]
}
let initialSate : ISearch ={
search : [] as SearchingInter[]
}
export const reducer =(state =initialSate , action:any) :ISearch =>{
switch(action.type){
case searchAction.FETCH_SUCCESS :
return {
...state,
search : action.payload
};
default : return state;
}
}
import React, { ChangeEvent } from 'react';
import {useEffect} from 'react';
import {useSelector,useDispatch} from 'react-redux';
import * as searchActions from '../Redux/SearchFetch/SearchAction';
import * as searchReducsers from '../Redux/SearchFetch/Searchreducer';
import SearchingData from './SearchingData';
const Search = () => {
let dispatch = useDispatch();
let readingStateData : searchReducsers.ISearch = useSelector((state : {searchingData:searchReducsers.ISearch})=>{
return state.searchingData;
})
useEffect(() => {
console.log(readingStateData.search)
dispatch(searchActions.fetchUser());
}, [])
const handlesearching =(e:ChangeEvent<HTMLInputElement>)=>{
//console.log(e.target.value);
let defaultData = readingStateData.search;
//console.log(defaultData);
const filteredData = e.target.value ? defaultData.filter(user =>user.UserName.toLowerCase().startsWith(e.target.value)) : defaultData
}
return (
<React.Fragment>
<div className="container mt-3">
<div className="row">
<div className="col-md-3">
<div className="card">
</div>
</div>
</div>
</div>
<SearchingData handleSearch={handlesearching}/>
<table className="table table-hover text-center table-primary">
<thead className="text-black">
<tr>
<th>UserName</th>
<th>Phone</th>
<th>Email</th>
<th>Gender</th>
</tr>
</thead>
<tbody>
<React.Fragment>
{
readingStateData.search.map(user =>{
return(
<tr>
<td>{user.UserName}</td>
<td>{user.PhoneNumber}</td>
<td>{user.email}</td>
<td>{user.gender}</td>
</tr>
)
})
}
</React.Fragment>
</tbody>
</table>
</React.Fragment>
)
}
export default Search;
import { type } from 'os';
import React, { ChangeEvent } from 'react'
type searchData = {
handleSearch : (e:ChangeEvent<HTMLInputElement>) => void;
}
const SearchingData:React.FC<searchData> = ({handleSearch}) => {
const UpdateData =(e:ChangeEvent<HTMLInputElement>)=>{
//console.log(e);
handleSearch(e)
}
return (
<React.Fragment>
<div>
<input type="text" onChange={UpdateData} />
</div>
</React.Fragment>
)
}
export default SearchingData
You need to have a state which will hold the filterData value. And set the initialValue of the state to the Data from the store
const [ dataToDisplay, setDataToDisplay ] = useState(readingStateData?.search || []);
Add a second useEffect which looks for the change in the readingStateData?.search. Initially you have the search as [] but once there is data we need to sync that data with the components's internal state.
useEffect(() => {
if(readingStateData?.search?.length > 0){
setDataToDisplay(readingStateData?.search)
}
}, [readingStateData?.search])
Now inside your handleChange you can update the state
const handlesearching =(e:ChangeEvent<HTMLInputElement>)=>{
const newDataToDisplay = e.target.value ? dataToDisplay.filter(user =>user.UserName.toLowerCase().startsWith(e.target.value)) : readingStateData?.search
setDataToDisplay(newDataToDisplay);
}
Now while rendering map over this dataToDisplay instead readingStateData?.search
dataToDisplay.map((user) => {
return (
<tr>
<td>{user.UserName}</td>
<td>{user.PhoneNumber}</td>
<td>{user.email}</td>
<td>{user.gender}</td>
</tr>
);
});
you can make your input as controlled input and have its value being read from the state
const Search = () => {
let dispatch = useDispatch();
let readingStateData: searchReducsers.ISearch = useSelector(
(state: {searchingData: searchReducsers.ISearch}) => {
return state.searchingData;
}
);
const [searchText, setSearchText] = useState('');
useEffect(() => {
console.log(readingStateData.search);
dispatch(searchActions.fetchUser());
}, []);
const handlesearching = (e: ChangeEvent<HTMLInputElement>) => {
setSearchText(e.target.value);
};
const dataToDisplay = searchText.trim().length > 0
? readingStateData?.search.filter((user) =>
user.UserName.toLowerCase().startsWith(searchText)
)
: readingStateData?.search;
return (
<React.Fragment>
<SearchingData handleSearch={handlesearching} searchText={searchText} />
{dataToDisplay.map((user) => {
return (
....
);
})}
</React.Fragment>
);
};
// In your Search Component add another prop called searchText
type searchData = {
handleSearch : (e:ChangeEvent<HTMLInputElement>) => void;
searchText: string;
}
const SearchingData:React.FC<searchData> = ({handleSearch, searchText}) => {
const UpdateData =(e:ChangeEvent<HTMLInputElement>)=>{
//console.log(e);
handleSearch(e)
}
return (
<React.Fragment>
<div>
<input type="text" value={searchText} onChange={UpdateData} />
</div>
</React.Fragment>
)
}
export default SearchingData
Using react hooks, I'm making a call to an api and displaying items in the app component calling a book and pagination functional component.
I have a search component placed at the top in the App return. Can anyone please help:
When the search button is clicked after inserting a book name, then books with similar names should be displayed
const SearchBooks =() => {
return (
<InputGroup>
<FormControl
type="text"
placeholder="Search books"
onChange={e => (e.target.value)}
/>
<InputGroup.Append>
<Button >
Search
</Button>
</InputGroup.Append>
</InputGroup>
);
}
const Book = ({books, loading}) => {
if(loading) {
return <h2>Loading...</h2>
}
return (books.map((book) =>
<ListGroup className="text-primary" key={book.id}>
<ListGroup.Item>
<h4>{book.book_title}</h4>
<li>Author : {book.book_author}</li>
<li>Publication Year : {book.book_publication_year}</li>
<li>Publication Country : {book.book_publication_country}</li>
<li>Publication City : {book.book_publication_city}</li>
<li >Pages : {book.book_pages}</li>
</ListGroup.Item>
</ListGroup>
));
}
const App = () => {
const [books, setBooks] = useState([]);
const [loading, setLoading] = useState(false);
const [currentPage, setCurrentPage] = useState(1);
const [booksPerPage] = useState(2);
const [search, setSearch] = useState('');
useEffect(() => {
const fetchBooks = async () => {
setLoading(true);
const res = await axios.post("http://nyx.vima.ekt.gr:3000/api/books");
setBooks(res.data.books);
setLoading(false);
};
fetchBooks();
}, []);
// Get current books
const indexOfLastBook = currentPage * booksPerPage;
const indexOfFirstBook = indexOfLastBook - booksPerPage;
const currentPosts = books.slice(indexOfFirstBook, indexOfLastBook);
// Change page
const paginate = pageNumber => setCurrentPage(pageNumber);
return (
<div className='container mt-5'>
<SearchBook/>
<Book books={currentPosts} loading={loading}/>
<Pagination
booksPerPage={booksPerPage}
totalBooks={books.length}
paginate={paginate}
/>
</div>
);
}
import React from 'react';
const Pagination = ({ booksPerPage, totalBooks, paginate }) => {
const pageNumbers = [];
for (let i = 1; i <= Math.ceil(totalBooks / booksPerPage); i++) {
pageNumbers.push(i);
}
return (
<nav className="justify-content-center">
<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>
);
};
const currentPosts = books.fliter(book => book.title.includes(keyword)).slice(indexOfFirstBook, indexOfLastBook);
and you should recalculate the pagination and reset page number too, so ppl can still navigate to pages if the search result is too long.
you can also use useMemo hooks to optimize it, so it wont filter the array again on every re-render.
const currentPosts = useMemo(() => books.filter(...).slice(...), [books, keyword]);