Cannot read property 'localeCompare' of undefined - reactjs

I am trying to sort the HTML table, but getting the error. Is there anything I am doing wrongly?
I want to able to sort ascending and descending. I am using useMemo. To make it, I am sorting on search return called searchPosts and I have sortType and setSortType defined in my useState.
import React, {useState, useEffect, useMemo} 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("");
const [sortType, setSortType] = useState('asc');
async function getData()
{
let response = await fetch('https://api.github.com/users');
let data = await response.json();
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);
// changd page
const paginate = (pageNumber) => setCurrentPage(pageNumber);
// Search Table
const handleFilterChange = e => {
setSearch(e.target.value)
}
// for search
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)
);
}
const searchPosts = DataSearch(currentPosts);
// **for Sorting**
const sorted = useMemo(() => {
if(!sortType) return searchPosts;
return searchPosts.slice().sort((a, b) => a[searchPosts].localeCompare(b[searchPosts]));
}, [searchPosts, sortType]);
return (
<div className="container">
<div>
<div>
<input value={search} onChange={handleFilterChange} placeholder={"Search"} />
</div>
<div> {loading }</div>
<table>
<thead>
<tr>
<th>id</th>
<th>avatar_url</th>
<th>events_url</th>
<th>followers_url</th>
<th>following_url</th>
<th>gists_url</th>
<th>gravatar_id</th>
<th>html_url</th>
<th>
login
<a onClick={() => setSortType("login")}> Sort by Login</a>
</th>
<th>node_id</th>
<th>organizations_url</th>
<th>received_events_url</th>
<th>repos_url</th>
<th>site_admin</th>
<th>starred_url</th>
<th>subscriptions_url</th>
<th>type</th>
<th>url</th>
</tr>
</thead>
<tbody>
{
sorted.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
I saw from stackoverflow the sorting example and I copied it to work with the code I have already.

First locate this line
const [sortType, setSortType] = useState('asc');
change it to
const [sortType, setSortType] = useState(null);
Now go to, replace with the code below
const sorted = useMemo(() => {
if(!sortType) return searchPosts;
return searchPosts.slice().sort((a, b) => a[sortType].localeCompare(b[sortType]));
}, [searchPosts, sortType]);
The mistake you did was the searchPosts instead of sortType
Then in your table, go
<th>
login
<a onClick={() => setSortType("login")}> Sort by Login</a>
</th>
You shouldn't have any error.

Related

Reacjs map data from api to table get while blank screen

I have a data called person in JSON format getting from API in the file User.js:
const [person, setPerson] = useState([]);
const url = "http://localhost:8080/api/persons";
useEffect(() => {
axios.get(url).then((response) => {
setPerson(response.data);
});
}, [url]);
In another file called UpdatePersonForm.js I'm trying to show that data in popup windows after clicking a button.
export const UpdatePersonForm= ({ person, personEditOnSubmit }) => {
return (
<div>
{person.map((item) => (
<tr>
<td>{item.name}</td>
</tr>
))}
</div>
}
then it shows a white blank screen again. If I called an API directly from UpdatePersonForm.js then it works fine. For example:
export const UpdatePersonForm= ({ personEditOnSubmit }) => {
const [person, setPerson] = useState([]);
const url = "http://localhost:8080/api/persons";
useEffect(() => {
axios.get(url).then((response) => {
setPerson(response.data);
});
}, [url]);
return (
<div>
{person.map((item) => (
<tr>
<td>{item.name}</td>
</tr>
))}
</div>
}
However, if I get data from the parent file like the above then I got wrong. Anyone know what’s wrong?

Apply sorting on all table users instead of the users of a single page

I have a table with 2 columns containing users info. I have divided the the table users in multiple pages, so that table of each page only displays 15 users. I have also implemented sorting on this table, so that when I click on each column header, the table is sorted according to this column. Here is the code:
import React, { useState, useEffect } from 'react'
import { getUsers } from '../../services/userService'
const Table = () => {
const [users, setUsers] = useState([]);
const [currentUsers, setCurrentUsers] = useState([]);
const [search, setSearch] = useState('');
const [isSorted, setIsSorted] = useState(false);
const [valueHeader, setValueHeader] = useState({title: "",body: ""}); //Value header state
const [sortedUsers, setSortedUsers] = useState([]);
const pageItemCount = 15
const [pageCount, setPageCount] = useState(0)
const [currentPage, setCurrentPage] = useState(1)
useEffect(async () => {
try {
const response = await getUsers(search);
setUsers(response.data.users);
setPageCount(Math.ceil(response.data.users.length / pageItemCount))
setCurrentUsers(response.data.users.slice(0, pageItemCount))
} catch (error) { }
}, [search]);
const sortFn = (userA, userB) => {
// sort logic here, it can be whatever is needed
// sorting alphabetically by `first_name` in this case
return userA[valueHeader.body].localeCompare(userB[valueHeader.body]) //<== Use value of column header
}
useEffect(() => {
if (isSorted) {
setSortedUsers(currentUsers.slice().sort(sortFn))
} else {
setSortedUsers(currentUsers)
}
}, [isSorted, currentUsers, valueHeader]) //<== add valueHeader to dependency
const toggleSort = (target) => {
setIsSorted(!isSorted)
setValueHeader({
title: target,
body: target == "name" ? "first_name" : "mobile_number"
}) //<=== set state of value header
}
const changePage = (i) => {
setCurrentPage(i)
const startItem = ((i - 1) * pageItemCount) + 1
setCurrentUsers(users.slice(startItem - 1, (pageItemCount * i)))
}
const handleChange = (event, value) => {
changePage(value);
}
return (
<div dir='rtl' className='bg-background mt-10 px-5 rd1200:px-30 overflow-auto'>
<table className='w-full border-separate rounded-md'>
<thead>
<tr className='bg-text-secondary text-white shadow-sm text-center'>
<th className='p-2' onClick={()=>toggleSort("name")}>name</th>
<th className='p-2' onClick={()=>toggleSort("mobile")}>mobile</th>
</tr>
</thead>
<tbody>
{sortedUsers.map((item, index) =>
<tr key={item.id} className={index % 2 === 0 ? 'bg-white shadow-sm text-center' : 'bg-text bg-opacity-5 shadow-sm text-center'}>
<td className='text-text text-sm p-2'>{item.first_name}</td>
<td className='text-text text-sm p-2'>{item.mobile_number}</td>
</tr>
)}
</tbody>
</table>
<Pagination className="mt-2 pb-20" dir='ltr' page={currentPage} count={pageCount} onChange={handleChange} variant="outlined" shape="rounded" />
</div>
)
}
export default Table
The only problem is that, since I display only 15 users of the table in each page, when I click on the column header, only the users of that page is sorted, but I want to apply sorting on all users of the table (the users of all pages). Is it possible?
Edited code according to suggested answer:
import React, { useState, useEffect } from 'react'
import { getUsers } from '../../services/userService'
const Table = () => {
const [users, setUsers] = useState([]);
const [sortDirection, setSortDirection] = useState("asc");
const [search, setSearch] = useState('');
const pageItemCount = 15
const [pageCount, setPageCount] = useState(2);
const [currentPage, setCurrentPage] = useState(1);
useEffect(async () => {
try {
const response = await getUsers(search);
setUsers(response.data.users);
setPageCount(Math.ceil(response.data.users.length / pageItemCount));
} catch (error) { }
}, [search]);
useEffect(() => {
toggleSort("name");
}, [])
const startItem = (currentPage - 1) * pageItemCount + 1;
const pagedUsers = users.slice(startItem - 1, pageItemCount * currentPage);
const sortFn = (fieldToSort, direction) => (userA, userB) => {
if (direction === "asc")
return userA[fieldToSort].localeCompare(userB[fieldToSort]);
else return userB[fieldToSort].localeCompare(userA[fieldToSort]);
};
const toggleSort = (target) => {
const direction = sortDirection === "asc" ? "desc" : "asc";
const fieldToSort = target === "name" ? "first_name" : "mobile_number";
setSortDirection(direction);
setUsers(users.slice().sort(sortFn(fieldToSort, direction)));
};
const handleChange = (event, value) => {
setCurrentPage(value);
};
return (
<div dir='rtl' className='bg-background mt-10 px-5 rd1200:px-30 overflow-auto'>
<table className='w-full border-separate rounded-md'>
<thead>
<tr className='bg-text-secondary text-white shadow-sm text-center'>
<th className='p-2' onClick={()=>toggleSort("name")}>name</th>
<th className='p-2' onClick={()=>toggleSort("mobile")}>mobile</th>
</tr>
</thead>
<tbody>
{pagedUsers?.map((item, index) =>
<tr key={item.id} className={index % 2 === 0 ? 'bg-white shadow-sm text-center' : 'bg-text bg-opacity-5 shadow-sm text-center'}>
<td className='text-text text-sm p-2'>{item.first_name}</td>
<td className='text-text text-sm p-2'>{item.mobile_number}</td>
</tr>
)}
</tbody>
</table>
{users.length > 0 && (
<Pagination
className="mt-2 pb-20"
dir="ltr"
page={currentPage}
count={pageCount}
onChange={handleChange}
variant="outlined"
shape="rounded"
/>
)}
</div>
)
}
export default Table
You have 2 methods
the first is sorting the users array but this will change the users in this page
The second is wich i prefere is to call the sorting function in the changepage function
The error it's in this useEffect logic:
useEffect(() => {
if (isSorted) {
// When its sorted, you are setting to sort currentUsers,
// and it holds only the first 15 users, not the entire users array
setSortedUsers(currentUsers.slice().sort(sortFn))
} else {
setSortedUsers(currentUsers)
}
}, [isSorted, currentUsers, valueHeader])
The below code should work for you:
useEffect(() => {
if (isSorted) {
// get all users and sort it,
// returning an immutable array because the use of .slice()
const usersUpdated = users.slice().sort(sortFn).slice(0, pageItemCount);
// Updated the currentUsers and sortedUsers states
setSortedUsers(usersUpdated);
setCurrentUsers(usersUpdated);
} else {
// Updated the currentUsers and sortedUsers states with the first 15 users
setSortedUsers(users.slice(0, pageItemCount));
setCurrentUsers(users.slice(0, pageItemCount));
}
// instead call the useEffect base on currentUsers, you change it to users
}, [isSorted, users, valueHeader]);
Having said that, just a point - You are using to many states for user:
. one for all users
. one for current users
. one for sorted users
You can handle this with only one state, i did a code sample to you check it.
The main cause of your issue is the line:
setSortedUsers(currentUsers.slice().sort(sortFn))
when I click on the column header, only the users of that page is sorted
It's not that strange that only the users on the current page are sorted, since that's exactly what the line of code above does.
Instead you want to sort first, then take the currentUsers from the sortedUsers.
I've written an answer, but did overhaul your code. The reason being is that you violate the single source of truth principal. Often resulting in bugs or strange behaviour due to a mismatch between the different sources of truth.
Examples of source of truth duplication is the fact that you store 3 lists of users users, currentUsers, and sortedUsers. The pageCount is essentially stored 2 times. One can be calculated by Math.ceil(users.length / pageItemCount), the other is stored in the pageCount state.
What happens if you change the users array length, but forget to adjust the pageCount?
Instead of storing the pageCount in a state, you can derive it from two available values. So there is no need for a state, instead use useMemo.
const pageCount = useMemo(() => (
Math.ceil(users.length, pageItemCount)
), [users.length, pageItemCount]);
Similarly you could derive sortedUsers and currentUsers from users. If you have access to the order in which the users should be sorted, the current page, and the maximum page size.
I've extracted some of the logic into separate functions to keep the component itself somewhat clean. Since you haven't given us a snippet or environment to work with I'm not sure if the code below works. But it should hopefully give you some inspiration/insight on how to handle things.
import React, { useState, useEffect, useMemo, useCallback } from 'react';
import { getUsers } from '../../services/userService';
// Returns a new object without the given keys.
function without(object, ...excludeKeys) {
excludeKeys = new Set(excludeKeys);
return Object.fromEntries(
Object.entries(object).filter(([key]) => !excludeKeys.has(key))
);
}
// Compares the given property value in both users.
// Returns -1, 0, or 1, based on ascending comparison.
function compareUserProp(userA, userB, prop) {
const valueA = userA[prop];
const valueB = userB[prop];
if (typeof valueA === "string" && typeof valueB === "string") {
return valueA.localeCompare(valueB);
}
if (valueA < valueB) return -1;
if (valueA > valueB) return 1;
return 0;
}
function isEven(integer) {
return integer % 2 === 0;
}
function getUserTRClass(index) {
if (isEven(index)) {
return 'bg-white shadow-sm text-center';
} else {
return 'bg-text bg-opacity-5 shadow-sm text-center';
}
}
function Table({ maxPageSize = 15 }) {
const [users , setUsers ] = useEffect([]); // [{ first_name: "John", last_name: "Doe", age: 42 }]
const [search , setSearch ] = useEffect("");
const [order , setOrder ] = useEffect({}); // { last_name: "asc", age: "desc" }
const [currentPage, setCurrentPage] = useEffect(1);
const pageCount = useMemo(() => (
Math.ceil(users.length / maxPageSize)
), [users.length, maxPageSize]);
const sortedUsers = useMemo(() => {
const modifier = { asc: 1, desc: -1 };
return Array.from(users).sort((userA, userB) => {
for (const [prop, direction] of Object.entries(order)) {
const diff = compareUserProp(userA, userB, prop);
if (diff) return diff * modifier[direction];
}
return 0;
});
}, [users, order]);
const usersOnPage = useMemo(() => {
const zeroBasedPage = currentPage - 1;
const beginIndex = zeroBasedPage * maxPageSize;
const endIndex = beginIndex + maxPageSize;
return sortedUsers.slice(beginIndex, endIndex);
}, [sortedUsers, currentPage, maxPageSize]);
// Do not pass an async function directly to `useEffect`. `useEffect` expects
// a cleanup function or `undefined` as the return value. Not a promise.
useEffect(() => {
(async function () {
const response = getUsers(search);
setUsers(response.data.users);
// setCurrentPage(1); // optional, reset page to 1 after a search
})();
}, [search]);
const toggleSort = useCallback((prop) => {
const inverse = { "desc": "asc", "asc": "desc" };
setOrder((order) => {
const direction = order[prop] || "desc";
return { [prop]: inverse[direction], ...without(order, prop) };
});
}, []);
const changePage = useCallback((_event, newPage) => {
setCurrentPage(newPage);
}, []);
return (
<div dir='rtl' className='bg-background mt-10 px-5 rd1200:px-30 overflow-auto'>
<table className='w-full border-separate rounded-md'>
<thead>
<tr className='bg-text-secondary text-white shadow-sm text-center'>
<th className='p-2' onClick={() => toggleSort("first_name")}>name</th>
<th className='p-2' onClick={() => toggleSort("mobile_number")}>mobile</th>
</tr>
</thead>
<tbody>
{usersOnPage.map((user, index) => (
<tr key={user.id} className={getUserTRClass(index)}>
<td className='text-text text-sm p-2'>{user.first_name}</td>
<td className='text-text text-sm p-2'>{user.mobile_number}</td>
</tr>
))}
</tbody>
</table>
<Pagination className="mt-2 pb-20" dir='ltr' page={currentPage} count={pageCount} onChange={changePage} variant="outlined" shape="rounded" />
</div>
);
}
export default Table;
If you want to start the users of sorted simply set an initial value for order. For example:
const [order, setOrder] = useState({ last_name: "asc" });
The algorithm to solve this would be as follows
Maintain one state for your entire dataset.
state: allUsers
Capture the event of button click.
Applying sorting to the entire data based on event handler inputs you can decide the sort criterion.
allUsers.sort(criterionFunction);
// you may call an API for this step and bind result to allUsers if needed or do it on the client side.
Derive the slice of data set based on the limit and offset maintained in the local state.
usersInPage = allUsers.slice(offset,limit)
The derived data slice shall re-render itself on the pagination UI.
renderUsers(usersInPage)

Log one field from each element of an array

I have objects, in the database. I want to grab the userId using axios, but when I tried to console.log() it. It shows undefined. hen I hardcoded it and targeted it by array, it shows.
How can I console log all of userId? I would like to grab it so I can use it as an endpoint for my database
const res = await userRequest.get('user/find/'+userId)
I want to grab the userId only.
import React, { useEffect, useState } from 'react'
import { format } from 'timeago.js'
import { userRequest } from '../../requestMethod'
import './Widgetlg.css'
const WidgetLg = () => {
const Button = ({ type }) => {
return <button className={'widgetLgButton ' + type}>{type}</button>
}
const [orders, setOrders] = useState([])
const [users, setUsers] = useState([])
useEffect(() => {
const getOrders = async () => {
//this is just a shorcut api
try {
const res = await userRequest.get('orders')
setOrders(res.data)
console.log(res.data?.userId)
console.log(res.data)
console.log(res.data[0].userId)
} catch (error) {
console.log(error)
}
}
getOrders()
}, [])
useEffect(() => {
const getUsername = async () => {
try {
const res = await userRequest.get('user/find/')
setUsers(res.data)
} catch (error) {}
}
getUsername()
}, [])
return (
<div className="widgetLg">
<h3 className="widgetLgTitle">Latest Transactions</h3>
<table className="widgetTable">
<tr className="widgetLgTr">
<th className="widgetLgTh">Customer</th>
<th className="widgetLgTh">Date</th>
<th className="widgetLgTh">Amount</th>
<th className="widgetLgTh">Status</th>
</tr>
{orders.map((order) => (
<tr className="widgetLgTr">
<td className="widgetLgUser">
<span className="WidgetLgName"> **I want here to show the username** </span>
</td>
<td className="widgetLgDate"> {format(order.createdAt)} </td>
<td className="widgetLgAmmount">P {order.amount} </td>
<td className="widgetLgStatus">
<Button type={order.status} />
</td>
</tr>
))}
</table>
</div>
)
}
export default WidgetLg
You could try something like this if I understand you correctly
const userIdsArray = res.data.map(d => d.userId);
console.log(userIdsArray);
res.data is an array. To log all elements, you could just iterate over them:
res.data.forEach(el => console.log(el.userId));
The reason that console.log(res.data) gives undefined is that the array itself doesn't have a userId field, only the elements of the array do.

Set the local state using useEffect on Redux prop change

I am new to React Redux and I am trying to setState on a prop change in Redux using a useEffect hook.
I have the following code:
const DeploymentOverview = ({diagram, doSetDiagram}) => {
const { diagram_id } = useParams()
const [instances, setinstances] = useState(null)
const [error, seterror] = useState([false, ''])
useEffect(() => {
GetDiagram(diagram_id).then(d => doSetDiagram(d)).catch(err => seterror([true, err]))
}, [doSetDiagram])
useEffect(() => {
if (diagram) {
if (diagram.instances) {
let statusList = []
diagram.instances.forEach(instance => {
InstanceStatus(instance.key)
.then(status => statusList.push(status))
.catch(err => seterror([true, err]))
});
setinstances(statusList)
}
}
}, [diagram])
return (
<Container>
{error[0] ? <Row><Col><Alert variant='danger'>{error[1]}</Alert></Col></Row> : null}
{instances ?
<>
<Row>
<Col>
<h1>Deployment of diagram X</h1>
<p>There are currently {instances.length} instances associated to this deployment.</p>
</Col>
</Row>
<Button onClick={setinstances(null)}><FcSynchronize/> refresh status</Button>
<Table striped bordered hover>
<thead>
<tr>
<th>Status</th>
<th>Instance ID</th>
<th>Workflow</th>
<th>Workflow version</th>
<th>Jobs amount</th>
<th>Started</th>
<th>Ended</th>
<th></th>
</tr>
</thead>
<tbody>
{instances.map(instance =>
<tr>
<td>{ <StatusIcon status={instance.status}/> }</td>
<td>{instance.id}</td>
{/* <td>{instance.workflow.name}</td>
<td>{instance.workflow.version}</td> */}
{/* <td>{instance.jobs.length}</td> */}
<td>{instance.start}</td>
<td>{instance.end}</td>
<td><a href='/'>Details</a></td>
</tr>
)}
</tbody>
</Table>
</>
: <Loader />}
</Container>
)
}
const mapStateToProps = state => ({
diagram: state.drawer.diagram
})
const mapDispatchToProps = {
doSetDiagram: setDiagram
}
export default connect(mapStateToProps, mapDispatchToProps)(DeploymentOverview)
What I want in the first useEffect is to set de Redux state of diagram (this works), then I have a other useEffect hook that will get a list from one of the diagrams attributes named instances next I loop over those instances and do a fetch to get the status of that instance and add this status to the statusList. Lastly I set the instances state using setinstances(statusList)
So now I expect the list of statusresults being set into instances and this is the case (also working?). But then the value is changed back to the initial value null...
In my console it's first shows null (ok, initial value), then the list (yes!) but then null again (huh?). I read on the internet and useEffect docs that the useEffect runs after every render, but I still don't understand why instances is set and then put back to it's initial state.
I am very curious what I am doing wrong and how I can fix this.
If you have multiple async operations you can use Promise.all:
useEffect(() => {
if (diagram) {
if (diagram.instances) {
Promise.all(
diagram.instances.map((instance) =>
InstanceStatus(instance.key)
)
)
.then((instances) => setInstances(instances))
.catch((err) => setError([true, err]));
}
}
}, [diagram]);
Here is a working example:
const InstanceStatus = (num) => Promise.resolve(num + 5);
const useEffect = React.useEffect;
const App = ({ diagram }) => {
const [instances, setInstances] = React.useState(null);
const [error, setError] = React.useState([false, '']);
//the exact same code from my answer:
useEffect(() => {
if (diagram) {
if (diagram.instances) {
Promise.all(
diagram.instances.map((instance) =>
InstanceStatus(instance.key)
)
)
.then((instances) => setInstances(instances))
.catch((err) => setError([true, err]));
}
}
}, [diagram]);
return (
<pre>{JSON.stringify(instances, 2, undefined)}</pre>
);
};
const diagram = {
instances: [{ key: 1 }, { key: 2 }, { key: 3 }],
};
ReactDOM.render(
<App diagram={diagram} />,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
What you did wrong is the following:
diagram.instances.forEach(instance => {
InstanceStatus(instance.key)//this is async
//this executes later when the promise resolves
//mutating status after it has been set does not
//re render your component
.then(status => statusList.push(status))
.catch(err => seterror([true, err]))
});
//this executes immediately so statusList is empty
setinstances(statusList)

Component doesnt rerender on state change

My problem is that my component doesnt rerender, when my state changes. I am managing my state in a custom Hook and after an put request to my backend my state gets updated. This works completely fine, but the content of my page doesnt get refreshed when changing my sate after the put request.
Component:
import React, { useEffect, useState } from 'react';
import { CONTROLLERS, useBackend } from '../../hooks/useBackend';
import Loading from '../Alerts/loading';
import {Table} from 'react-bootstrap';
import 'bootstrap/dist/css/bootstrap.min.css';
import DropdownForm from '../Forms/dropdown';
function AdminPanel() {
const headers = ['ID', 'Title', 'Release Date', 'Producer', 'Director', 'Status', 'UTC Time', '#', '#'];
const [error, setError] = useState(false);
const [loaded, setLoaded] = useState(false);
const [requests, backend] = useBackend(error, setError);
useEffect(() => {
backend(CONTROLLERS.REQUESTS.getRequestsAdmin());
}, [])
useEffect(() => {
setLoaded(requests !== undefined);
console.log(requests);
}, [requests])
const handleUpdate = (e, result) => {
backend(CONTROLLERS.REQUESTS.put({requestStatus: result, accessToken: localStorage.accessToken}, e));
}
if(!loaded) return <Loading/>
if(error) return <p>No Access</p>
return(
<>
<DropdownForm items={['A-Z', 'Z-A', 'None']} title={'Filter'} first={2} setHandler={setFilter}/>
<DropdownForm items={['+20', '+50', 'All']} title={'Count'} first={0} setHandler={setCount}/>
{/* <DropdownForm/> */}
<Table bordered hover responsive="md">
<thead>
<tr>
{headers.map((item, index) => {
return( <th className="text-center" key={index}>{item}</th> );
})}
</tr>
</thead>
<tbody>
{requests.map((item, index) =>{
return(
<tr>
<td>{index + 1}</td>
<td>{item.movie.movieTitle}</td>
<td>{item.movie.movieReleaseDate}</td>
<td>{item.movie.movieProducer}</td>
<td>{item.movie.movieDirector}</td>
<td>{(item.requestStatus === 1 ? 'Success' : item.requestStatus ===2 ? 'Pending' : 'Denied')}</td>
<td className="col-md-3">{item.requestDate}</td>
{/* <td><span onClick={() => handleDelete(item.requestID)}><i className="fas fa-times"></i></span></td> */}
<td><span onClick={() => handleUpdate(item.requestID, 3)}><i className="fas fa-times"></i></span></td>
<td><span onClick={() => handleUpdate(item.requestID, 1)}><i className="fas fa-check"></i></span></td>
</tr>);
})}
</tbody>
</Table>
</>
);
}
// }
export default AdminPanel;
customHook:
import axios from "axios";
import { useEffect, useRef, useState } from "react";
import notify from "../Components/Alerts/toasts";
const BASE_URL = 'https://localhost:44372/api/';
const R = 'Requests/'; const M = 'Movies/'; const U = 'Users/';
const buildParams = (url, type, header, param) => {
return {url: url, type: type, header: header, param: param};
}
export const CONTROLLERS = {
REQUESTS: {
getRequestsAdmin: () => buildParams(`${R}GetRequestsAdmin`, 'post', true, {accessToken:
}
export const useBackend = (error, setError) => {
const [values, setValues] = useState([]);
async function selectFunction(objc) {
switch(objc.type) {
case 'put': return buildPutAndFetch(objc.url, objc.param, objc.header);break;
default: console.log("Error in Switch");
}
}
async function buildPutAndFetch(url, param, header) {
const finalurl = `${BASE_URL}${url}`;
return axios.put(finalurl, param, {headers: {
'Authorization': `Bearer ${(localStorage.accessToken)}`
}})
.then(res => {
if(res.data && 'accessToken' in res.data) localStorage.accessToken = res.data.accessToken;
else {
//When an object gets updated, the backend returns the updated object and replaces the old one with the //new one.
const arr = values;
const found = values.findIndex(e => e[(Object.keys(res.data))[0]] == res.data.requestID);
arr[found] = res.data;
setValues(arr);
}
setError(false);
return true;
})
.catch(err => {
setError(true);
return false;
})
}
}
function response(res) {
setValues(res.data)
setError(false);
}
return [values,
async (objc) => selectFunction(objc)];
}
It's likely due to the fact that your buildPutAndFetch function is mutating the values array in state, rather than creating a new reference. React will bail out on state updates if the reference doesn't change.
When you declare your arr variable, it's setting arr equal to the same reference as values, rather than creating a new instance. You can use the spread operator to create a copy: const arr = [...values].
It's also worth noting that because this is happening asynchronously, you may want to use the function updater form of setValues to ensure you have the most current set of values when performing the update.
setValues(prev => {
const arr = [...prev];
const found = prev.findIndex((e) => e[Object.keys(res.data)[0]] == res.data.requestID);
arr[found] = res.data;
return arr;
});

Resources