I am making a request to a local server. And server returns to me the follow response:
{"total":7,
"perPage":3,
"page":1,
"lastPage":3,
"data":[
{"id":1,"title":"animals","created_at":"/...","updated_at":"/..."},
{"id":2,"title":"space","created_at":"/...","updated_at":"/..."},
{"id":3,"title":"sport","created_at":"/...","updated_at":"/..."}
]}
data - it list of categories. And I display my list in the form of a table.
total, perPage, page, lastPage - it query param which I will insert into URL to filter my list.
page - the current page number. If in field page would be number two, then it would be in data the other three objects. That is, there would be in data other categories, with other title and id.
I have a task:
make pagination with three buttons. Because I have three pages "lastPage":3.And how I implemented it (I comment some line):
file Home.js:
const Home = () => {
const [value, setValue] = useState({
listCategory: [], // here I put my list of categories
currentPage: 1 // here I set initial page
});
useEffect(() => {
fetchData();
},[]);
async function fetchData() {
try {
const res = await apiCategory('api/categories', { //apiCategory it function I'll write it below
method:'GET'
});
console.log(res);
setValue({
listCategory:res.data, // I put the received data in an empty array to display the category list on the page
currentPage:res.page // set page which is default in response from server, now it-("page":1)
});
} catch(e) {
console.error(e);
}
};
const changePage = (argPage) => {
setValue({
currentPage: argPage // change page depending on which button was pressed
});
}
return (
<div>
<Search/>
<Table dataAttribute = {value.listCategory} /> // dispaly category list
{Object.keys(value.currentPage).map((item, index) => (
<button key={item} onClick={() => changePage(index)}>{item}</button>
))} // dispaly button and attach method changePage
</div>
)}
There is function apiCategory:
export const apiCategory = async (url, args) => {
const getToken = localStorage.getItem('myToken');
const valuePage = value.currentPage; //currentPage- props of my state in useState
const response = await fetch(`${apiUrl}${url}?page=${valuePage}`, { //I substitute in URL value from valuePage that implement pagination
...args,
headers: {
"Content-type": "application/json; charset=UTF-8 ",
"Authorization": `Bearer ${getToken}`,
"Accept": 'application/json',
...args.headers,
},
});
return response.json();
}
But I did not have buttons that switch pages (pagination). Only category list displays(screenshot):
https://i.piccy.info/i9/0b04d1777b6769e3a4dcb500faa3554a/1587741736/24398/1372209/Screenshot_1.png
Maybe even they do not change their condition. I don't know...
Also in function apiCategory I have an error:
'value' is not defined no-undef
in this line:
const valuePage = value.currentPage;
What should I change in the code so that buttons appear and pagination works?
I will also write a file Table.js:
export default ({dataAttribute}) => (
<table className="table">
<thead className="table-head">
<tr >
<th>id</th>
<th>title</th>
<th>created_at</th>
<th>updated_at</th>
</tr>
</thead>
<tbody>
{dataAttribute.map(item => (
<tr key={item.id}>
<td>{item.id}</td>
<td>{item.title}</td>
<td>{item.created_at}</td>
<td>{item.updated_at}</td>
</tr>
))}
</tbody>
</table>
);
You need to pass value into the apiCategory function, otherwise it is undefined. If you want fetchData to fire every time the next page is selected then it needs to be a dependency in useEffect.
Home.js
useEffect(() => {
async function fetchData(currentPage) {
try {
const res = await apiCategory('api/categories', {
method:'GET'
}, currentPage);
console.log(res);
setValue({
listCategory:res.data
currentPage:res.page
});
} catch(e) {
console.error(e);
}
};
fetchData(value.currentPage);
},[value.currentPage]);
apCategory.js
export const apiCategory = async (url, args, valuePage) => {
...
// remove the local valuePage line
Also note that you are removing the listCategory property in value every time changePage is called. To preseve the previous values, you need to change it to this:
const changePage = (argPage) => {
setValue((prev) => {
return {
...prev,
currentPage: argPage
}
});
}
In your return you aren't referring to the data structure properly.
{
// iterate through each element in `value.ListCategory`
value.listCategory.map((item, index) => (
<button
key={'listCategory_'+item.id}
onClick={() => changePage(index + 1)}
>
{index + 1}
</button>
))
}
Related
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?
My problem here is that I want to make a like,dislike, hearth and etc buttons, but my problem is that the clicks that I count will upload it to the database but I have to reload it after so it will show up to the client side.
Here is my sample code.
For backend
router.route('/update/:id').post((req,res) => {
practice1.findById(req.params.id)
.then( practice_1 => {
practice_1.name = req.body.name
practice_1.number = req.body.number
practice_1.save()
.then(() => res.json('Sample Updated!'))
.catch(err => res.status(400).json('Error :' + err))
})
.catch()
})
router.route('/update/:id').put((req,res) => {
practice1.findByIdAndUpdate(req.params.id)
.then(practice_1 => {
practice_1.name = req.body.name
practice_1.number = req.body.number
practice_1.save()
.then(() => res.json('Sample Updated!'))
.catch(err => res.status(400).json('Error :' + err))
})
.catch(err => res.status(400).json('Error :' + err))
})
I did tried to do it in post() and its working though..but It is doing the same thing as the put() where it only updates after I reload the page..
Here is the code for the front end.
import axios from 'axios'
import React, { useEffect, useState } from 'react'
const Samples = props => (
<tr>
<th>{props.sample._id}</th>
<th>{props.sample.name}</th>
<th>{props.sample.number}</th>
<th>
<button onClick={e => props.addMore(props.sample._id,props.sample)}> Add number </button>
</th>
</tr>
)
function ListSample() {
const [samples,setSamples] = useState([])
// const [sampleadd,setSampleAdd] = useState(0)
useEffect(() => {
axios.get('http://localhost:1212/practice1')
.then(data => setSamples(data.data))
.catch(err => console.log(err))
},[])
const addMore = (id,samples) => {
samples.number += 1
axios.post('http://localhost:1212/practice1/update/'+id,{ name:samples.name , number: samples.number })
.then(res => console.log(res.data))
.catch(err => console.log(err))
axios.put('http://localhost:1212/practice1/update/'+id, { name: samples.name, number: samples.number} )
.then(res => console.log(res.data))
.catch(err => console.log(err))
}
const sampleDeclarations = () => {
return samples.map(currentsample => {
return <Samples
key={currentsample._id}
sample={currentsample}
addMore={ e => addMore(e,currentsample)}
></Samples>
})
}
return (
<div>
<h1> Sample List </h1>
<table>
<thead>
<tr>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
{sampleDeclarations()}
</tbody>
</table>
</div>
)
}
export default ListSample
So basically what I did here in the part of addMore() As you see I samples to add the samples.number += 1 and then give it to put or post..You can just comment out either put or post axios.. I tried it already..but I basically want to automatically upload my count of likes without reloading.
Basically like this in this picture.
Yeah sorry about the namings but just don't mind it.. I just wanna execute it in right method.
Since this is a state hook, you have to use setSamples. After you do samples.number += 1, try doing
setSamples({
...samples,
number = samples.number + 1
});
Where ...samples does an object spread getting the rest of the values of the original samples and you are just changing the number parameter. Additionally, maybe change the inside of useState([]) from an empty array to an empty object {} since samples seems to be an object.
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;
});
I am using react-table to display fetched data within a table. You also have different buttons within that table to interact with the data such as deleting an entry, or updating its data (toggle button to approve a submitted row).
The data is being fetched in an initial useEffect(() => fetchBars(), []) and then being passed to useTable by passing it through useMemo as suggested in the react-table documentation. Now I can click on the previously mentioned buttons within the table to delete an entry but when I try to access the data (bars) that has been set within fetchBars()it returns the default state used by useState() which is an empty array []. What detail am I missing? I want to use the bars state in order to filter deleted rows for example and thus make the table reactive, without having to re-fetch on every update.
When calling console.log(bars) within updateMyData() it displays the fetched data correctly, however calling console.log(bars) within handleApprovedUpdate() yields to the empty array, why so? Do I need to pass the handleApprovedUpdate() into the cell as well as the useTable hook as well?
const EditableCell = ({
value: initialValue,
row: { index },
column: { id },
row: row,
updateMyData, // This is a custom function that we supplied to our table instance
}: CellValues) => {
const [value, setValue] = useState(initialValue)
const onChange = (e: any) => {
setValue(e.target.value)
}
const onBlur = () => {
updateMyData(index, id, value)
}
useEffect(() => {
setValue(initialValue)
}, [initialValue])
return <EditableInput value={value} onChange={onChange} onBlur={onBlur} />
}
const Dashboard: FC<IProps> = (props) => {
const [bars, setBars] = useState<Bar[]>([])
const [loading, setLoading] = useState(false)
const COLUMNS: any = [
{
Header: () => null,
id: 'approver',
disableSortBy: true,
Cell: (props :any) => {
return (
<input
id="approved"
name="approved"
type="checkbox"
checked={props.cell.row.original.is_approved}
onChange={() => handleApprovedUpdate(props.cell.row.original.id)}
/>
)
}
}
];
const defaultColumn = React.useMemo(
() => ({
Filter: DefaultColumnFilter,
Cell: EditableCell,
}), [])
const updateMyData = (rowIndex: any, columnId: any, value: any) => {
let barUpdate;
setBars(old =>
old.map((row, index) => {
if (index === rowIndex) {
barUpdate = {
...old[rowIndex],
[columnId]: value,
}
return barUpdate;
}
return row
})
)
if(barUpdate) updateBar(barUpdate)
}
const columns = useMemo(() => COLUMNS, []);
const data = useMemo(() => bars, [bars]);
const tableInstance = useTable({
columns: columns,
data: data,
initialState: {
},
defaultColumn,
updateMyData
}, useFilters, useSortBy, useExpanded );
const fetchBars = () => {
axios
.get("/api/allbars",
{
headers: {
Authorization: "Bearer " + localStorage.getItem("token")
}
}, )
.then(response => {
setBars(response.data)
})
.catch(() => {
});
};
useEffect(() => {
fetchBars()
}, []);
const handleApprovedUpdate = (barId: number): void => {
const approvedUrl = `/api/bar/approved?id=${barId}`
setLoading(true)
axios
.put(
approvedUrl, {},
{
headers: {Authorization: "Bearer " + localStorage.getItem("token")}
}
)
.then(() => {
const updatedBar: Bar | undefined = bars.find(bar => bar.id === barId);
if(updatedBar == null) {
setLoading(false)
return;
}
updatedBar.is_approved = !updatedBar?.is_approved
setBars(bars.map(bar => (bar.id === barId ? updatedBar : bar)))
setLoading(false)
})
.catch((error) => {
setLoading(false)
renderToast(error.response.request.responseText);
});
};
const renderTable = () => {
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow
} = tableInstance;
return(
<table {...getTableProps()}>
<thead>
{headerGroups.map(headerGroup => (
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map(column => (
<th {...column.getHeaderProps()}>
<span {...column.getSortByToggleProps()}>
{column.render('Header')}
</span>{' '}
<span>
{column.isSorted ? column.isSortedDesc ? ' ▼' : ' ▲' : ''}
</span>
<div>{column.canFilter ? column.render('Filter') : <Spacer/>}</div>
</th>
))}
</tr>
))}
</thead>
<tbody {...getTableBodyProps()}>
{rows.map(row => {
prepareRow(row)
const rowProps = {...row.getRowProps()}
delete rowProps.role;
return (
<React.Fragment {...rowProps}>
<tr {...row.getRowProps()}>
{row.cells.map(cell => {
return (
<td {...cell.getCellProps()}>{cell.render('Cell')}</td>
)
})}
</tr>
{row.isExpanded ? renderRowSubComponent({row}): null}
</React.Fragment>
)})
}
</tbody>
</table>
)
}
}
export default Dashboard;
You're seeing stale values within handleApprovedUpdate because it's capturing bars the first time the component is rendered, then never being updated since you're using it inside COLUMNS, which is wrapped with a useMemo with an empty dependencies array.
This is difficult to visualize in your example because it's filtered through a few layers of indirection, so here's a contrived example:
function MyComponent() {
const [bars, setBars] = useState([]);
const logBars = () => {
console.log(bars);
};
const memoizedLogBars = useMemo(() => logBars, []);
useEffect(() => {
setBars([1, 2, 3]);
}, []);
return (
<button onClick={memoizedLogBars}>
Click me!
</button>
);
}
Clicking the button will always log [], even though bars is immediately updated inside the useEffect to [1, 2, 3]. When you memoize logBars with useMemo and an empty dependencies array, you're telling React "use the value of bars you can currently see, it will never change (I promise)".
You can resolve this by adding bars to the dependency array for useMemo.
const memoizedLogBars = useMemo(() => logBars, [bars]);
Now, clicking the button should correctly log the most recent value of bars.
In your component, you should be able to resolve your issue by changing columns to
const columns = useMemo(() => COLUMNS, [bars]);
You can read more about stale values in hooks here. You may also want to consider adding eslint-plugin-react-hooks to your project setup so you can identify issues like this automatically.
I am learning REST API. I am using the react app for front end and backend for Node js and express server. For API I am using REST API. I am using MongoDB for the database. I successfully display all the data to the browser. I can able to search the data. Now I want to delete the data. I don't know how to delete data from REST API endpoint. I will be really glad if someone help me out. I tested my backend by using Postman. Everything works fine as expected.
This is my backend delete end point
app.delete("/students/:id", async (req, res, next) => {
const id = req.params.id;
try {
student
.remove({ _id: id })
.exec()
.then(data => {
res.json(data);
});
} catch (error) {
console.log(error);
}
});
I export my API END points to React js
export async function deleteStudent(id) {
const response = await fetch(`/students/${id}`, {
method: "DELETE"
});
return response.json();
}
This is the main component where I want to delete the data
import React, { useState, useEffect } from "react";
import { logEntry } from "../Api/Api";
import { deleteStudent } from "../Api/Api";
function Datatable() {
const [total, settotal] = useState([]);
const [searchItem, setsearchItem] = useState({
item: ""
});
const [data, setdata] = useState([]);
const handleChange = e => {
setsearchItem({ item: e.target.value });
};
const getEntries = async () => {
const logEntries = await logEntry();
console.log(logEntries);
settotal(logEntries.count);
setdata(logEntries.students);
};
const nameFilter = data.filter(list => {
return list.name.toLowerCase().includes(searchItem.item.toLowerCase());
});
const deleteData = async id => {
await deleteStudent(id);
};
useEffect(() => {
getEntries();
}, []);
return (
<div>
<div style={{ paddingLeft: "800px" }}>
<input
placeholder="Search student"
onChange={handleChange}
style={{ width: "200px", height: "30px" }}
/>
</div>
<p>Total student: {total} </p>
<table>
<thead>
<tr>
<th>Name</th>
<th>City</th>
<th>Address</th>
<th>Phone</th>
<th>Email</th>
</tr>
</thead>
<tbody>
{nameFilter === "" ? (
<p>Student not found</p>
) : (
nameFilter.map(list => {
return (
<tr>
<td>{list.name}</td>
<td>{list.city}</td>
<td>{list.address}</td>
<td>{list.phone}</td>
<td>{list.email}</td>
<td>
<a
className="waves-effect red btn-small"
onClick={() => deleteData(list.id)}
>
Delete
</a>
</td>
</tr>
);
})
)}
</tbody>
</table>
</div>
);
}
export default Datatable;
I don't know, Am I doing?
This looks like a great start! I'm operating on the understanding that you need to somehow pass the id of the student you want to delete into the URL in deleteStudent() from the 'DELETE' button in your <DataTable> component.
So, first, let's refactor your deleteStudent() function:
export async function deleteStudent(id) {
const response = await fetch(`/students/${id}`, {
method: "DELETE",
});
return response.json();
}
You don't need to send any data with a DELETE request, you just need to hit the correct URL based on the id, which we can pass in to the method and dynamically include in the fetch() call.
Now, you need to find some way to pass that id into the deleteStudent() function. From what I can see, you are pulling in the student data here (I've paraphrased this):
const getEntries = async () => {
// students are pulled in, I'm assuming they have an 'id' property that corresponds to the 'id' that MongoDB has them stored under
const logEntries = await logEntry();
// and now data references the students
setdata(logEntries.students);
};
It looks like then you filter the students here:
const nameFilter = data.filter(list => {
return list.name.toLowerCase().includes(searchItem.item.toLowerCase());
});
And then render the filtered students with a call to .map(). This is where you can pass the id along in the onClick handler, assuming that you DO have an id field on these list elements. If you don't, then you will need to find a way to add the id in to this data:
nameFilter.map(list => {
return (
<tr>
<td>{list.name}</td>
<td>{list.city}</td>
<td>{list.address}</td>
<td>{list.phone}</td>
<td>{list.email}</td>
<td>
<a
className="waves-effect red btn-small"
onClick={() => deleteData(list.id)} // this is where the id should get passed on to the handler, and then dynamically included in the DELETE /students/:id url
>
Delete
</a>
</td>
</tr>
);
})
Then, in your deleteData() function, you will receive the id as a param, and you can call your deleteStudent(id) function to make the request to the backend:
const deleteData = async id => {
await deleteStudent(id);
};
There are some other things that need work, but you have the general idea correct! I'll give some hints towards further improvements below.
Do these need to be separate, or can they be combined?
import { logEntry } from "../Api/Api";
import { deleteStudent } from "../Api/Api";
Maybe clean up the DELETE route-handler:
app.delete("/students/:id", async (req, res, next) => {
const id = req.params.id;
try {
// generally, Mongoose Model's are represented with TitleCase
Student
.remove({ _id: id })
.exec() // is this needed? https://mongoosejs.com/docs/api/model.html#model_Model-remove
.then(data => {
res.json(data);
});
} catch (error) {
console.log(error);
}
});
There seems to be some extra state/hooks lying around in this Datatable:
function Datatable() {
// ... bunch of code
// do you need state for this?
const [removeStudent, setremoveStudent] = useState([]);
// ... more code
const getEntries = async () => {
// ...
setremoveStudent(deleteData); // not sure this is needed...
};