I have page where I display all categories with this code
{categories.map((category, index) => {
return(
<tr key={index}>
<th scope="row">{index}</th>
<td>{category.name}</td>
<td>50</td>
<td><Link to={{pathname: `/categories/edit/`}}>Edit</Link></td>
<td><Button variant={'danger'} onClick={deleteCategoryHandler} data-id={category._id}>Delete</Button></td>
</tr>
)
})}
But when I create Component edit Category, and there I Need send with id. which will be better ID and name?
you need to do this:
categories.map((category, index) => {
const location = {
pathname: `/categories/edit/`,
state: {
category,
},
};
return (
<tr key={index}>
<th scope="row">{index}</th>
<td>{category.name}</td>
<td>50</td>
<td>
<Link
to={location}
>
Edit
</Link>
</td>
<td>
<Button
variant={"danger"}
onClick={deleteCategoryHandler}
data-id={category._id}
>
Delete
</Button>
</td>
</tr>
);
});
and in the destination component just use something like this to get location object and then its state property:
const location = useLocation();
const category = location?.state?.category;
If you need to send a category id, then an incredibly easy way is to send it as part of the path URL.
Create a route in your Router that handles path="/categories/edit/:id". Then construct the appropriate path for the link when mapping.
{categories.map((category, index) => {
return(
<tr key={index}>
<th scope="row">{index}</th>
<td>{category.name}</td>
<td>50</td>
<td>
<Link to={{pathname: `/categories/edit/${category.id}`}}>
Edit
</Link>
</td>
<td>
<Button
variant={'danger'}
onClick={deleteCategoryHandler}
data-id={category._id}
>
Delete
</Button>
</td>
</tr>
)
})}
On the receiving route's component the id parameter can be accessed via match object using the useParams react hook from react-router-dom.
const { id } = useParams();
Add category id variable in your link.
{categories.map((category, index) => {
return(
<tr key={index}>
<th scope="row">{index}</th>
<td>{category.name}</td>
<td>50</td>
<td><Link to={{pathname: `/categories/edit/ID/${category._id}`}}>Edit</Link></td>
<td><Button variant={'danger'} onClick={deleteCategoryHandler} data-id={category._id}>Delete</Button></td>
</tr>
)
})}
In your hook Component use useParam() to get category id
import {useParams} from "react-router-dom";
const YourComponent = (props) => {
let { ID } = useParams();
...
}
Related
Langage used : JS with REACT REDUX
The context : I have a component who render a list of quotes following the user filter and categories choice.
In my filter component, i store the select value (buttonsData), and here i re render a certains component depending on select value.
import React from 'react';
import { Table } from 'react-bootstrap';
import { useSelector } from 'react-redux';
//here each component following the user choice
import { AllForms } from './categories/AllForms';
import { AtoZ } from './sorted/AtoZ';
import { ZtoA } from './sorted/ZtoA';
import { Ascend } from './sorted/Ascend';
import CurrentOffers from './categories/CurrentOffers';
import ValidateOffers from './categories/ValidateOffers';
export const OfferList = () => {
const buttonsData = useSelector((state) => state.buttonReducer);
return (
<Table hover responsive="md" className="folder__table">
<thead className="folder__content">
<tr className="folder__titles">
<th className="folder__title"> </th>
<th className="folder__title">Order REF</th>
<th
className="folder__title"
>
Entité
</th>
<th className="folder__title">Customer</th>
<th className="folder__title">Status</th>
<th className="folder__title">Date</th>
<th className="folder__title "> </th>
</tr>
</thead>
{buttonsData.activeComponent === 'AllForms' && <AllForms />}
{buttonsData.activeComponent === 'Ascend' && <Ascend />}
{buttonsData.activeComponent === 'validate' && <ValidateOffers />}
</Table>
);
};
I have used createSelector to filter and sort my datas (working fine).
import { useSelector } from 'react-redux';
export const SelectOffersValidate = () => {
//here i select ALL my forms, get with axios
const formsDatas = useSelector((state) => state.offersReducer);
const sortedForms = [...formsDatas].filter(
(oneOffer) => oneOffer.status == 'validate'
);
console.log(sortedForms);
return sortedForms;
};
export const SelectOffersAscend = () => {
const formsDatas = useSelector((state) => state.offersReducer);
const sortedForms = [...formsDatas].sort((a, b) =>
b.createdAt.localeCompare(a.createdAt)
);
return sortedForms;
};
Here a component filtered ( i have one component for AllForms, one for Validate and one for ascend, exaclty the same but with own select function)
import React, { useState } from 'react';
import { FiEdit3 } from 'react-icons/fi';
import {
SelectOffersAscend,
} from '../../../selector/projects.selector.js';
import { isEmpty } from '../../../middlewares/verification.js';
import Moment from 'react-moment';
export const Ascend = () => {
const formsAscend = SelectOffersAscend();
return (
<>
<tbody>
{!isEmpty(formsAscend[0]) &&
formsAscend?.map((oneForm) => {
return (
<tr key={oneForm.id}>
<td>
<input
type="checkbox"
/>
</td>
<td>{oneForm.ref} </td>
<td> {oneForm.entity}</td>
<td>{oneForm.customer} </td>
<td>{oneForm.status} </td>
<td>
<Moment format="DD/MM/YYYY" date={oneForm.createdAt} />
</td>
<td>
<FiEdit3 />
</td>
</tr>
);
})}
</tbody>
</>
);
};
My first problem :
I have made a component for EACH filter, but it's repetitive, is there a better way to do ?
The second problem :
"AllForms" and "ValidateOffers" are categories and "Ascend" is a filter.
For the moment i filter only with AllForms but i would like to filtered based on categories choosen.
I've tried to create an action to store the actual categories, so i've tried to dispatch on my createSelector validate function but it's looping so i don't think is the best way to do
SOLUTION : thanks to Chris whol helped me :)
So i have delete all my filtered component to just have one and create a custom hook
import React, { useMemo } from 'react';
import { Table } from 'react-bootstrap';
import { useSelector } from 'react-redux';
import { OfferRows } from './OfferRows';
export const useFilteredOffers = () => {
const buttonsData = useSelector((state) => state.buttonReducer);
const offersData = useSelector((state) => state.offersReducer);
return useMemo(() => {
switch (buttonsData.activeComponent) {
case 'Ascend': // fix casing
return offersData?.sort((a, b) =>
b.createdAt.localeCompare(a.createdAt)
);
case 'validate':
return offersData?.filter((oneOffer) => oneOffer.status === 'validate');
case 'not validate':
return offersData?.filter(
(oneOffer) => oneOffer.status === 'not validate'
);
case 'AtoZ':
return offersData?.sort((a, b) => a.customer.localeCompare(b.customer));
case 'ZtoA':
return offersData?.sort((a, b) => b.customer.localeCompare(a.customer));
default:
return offersData;
}
}, [buttonsData.activeComponent, offersData]);
};
export const OfferList = () => {
const filteredOffers = useFilteredOffers();
return (
<Table hover responsive="md" className="folder__table">
<thead className="folder__content">
<tr className="folder__titles">
<th className="folder__title"> </th>
<th className="folder__title">Order REF</th>
<th className="folder__title">Entité</th>
<th className="folder__title">Customer</th>
<th className="folder__title">Status</th>
<th className="folder__title">Date</th>
<th className="folder__title "> </th>
</tr>
</thead>
<OfferRows offers={filteredOffers} />
</Table>
);
};
Here the rows
import React from 'react';
import { FiEdit3 } from 'react-icons/fi';
import Moment from 'react-moment';
import { isEmpty } from '../../middlewares/verification.js';
export const OfferRows = ({ offers }) => {
return (
<>
<tbody>
{!isEmpty(offers[0]) &&
offers?.map((oneForm) => {
return (
<tr key={oneForm.id}>
<td>
<input type="checkbox" />
</td>
<td>{oneForm.ref} </td>
<td> {oneForm.entity}</td>
<td>{oneForm.customer} </td>
<td>{oneForm.status} </td>
<td>
<Moment format="DD/MM/YYYY" date={oneForm.createdAt} />
</td>
<td>
<FiEdit3 />
</td>
</tr>
);
})}
</tbody>
</>
);
};
I would create a single component for the rendering of the offer rows. The data can be filtered using a single hook that also selects the active filter. You can also pass this down as an argument.
Custom hooks MUST start with the use keyword. See the Rules of Hooks documentation for more information.
const useFilteredOffers = () => {
const activeFilter = useSelector((state) => state.buttonReducer);
const offers = useSelector((state) => state.offersReducer);
return useMemo(() => {
switch (activeFilter) {
case 'Ascend': // fix casing
return offers?.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
case 'validate':
return offers?.filter(oneOffer => oneOffer.status == 'validate');
default:
return offers;
}
}, [activeFilter, offers]);
}
export const OfferList = () => {
const filteredOffers = useFilteredOffers();
return (
<Table hover responsive="md" className="folder__table">
<thead className="folder__content">
<tr className="folder__titles">
<th className="folder__title"> </th>
<th className="folder__title">Order REF</th>
<th
className="folder__title"
>
Entité
</th>
<th className="folder__title">Customer</th>
<th className="folder__title">Status</th>
<th className="folder__title">Date</th>
<th className="folder__title "> </th>
</tr>
</thead>
<OfferRows offers={filteredOffers} />
</Table>
);
};
For completeness, here is the OfferRows component.
PS: You won't need to use the isEmpty validator because Array#map won't have any effect when the Array is empty.
export const OfferRows = (offers) => {
return (
<>
<tbody>
{offers?.map((oneForm) => {
return (
<tr key={oneForm.id}>
<td>
<input
type="checkbox"
/>
</td>
<td>{oneForm.ref} </td>
<td> {oneForm.entity}</td>
<td>{oneForm.customer} </td>
<td>{oneForm.status} </td>
<td>
<Moment format="DD/MM/YYYY" date={oneForm.createdAt} />
</td>
<td>
<FiEdit3 />
</td>
</tr>
);
})}
</tbody>
</>
);
};
This is my code:
function EditCourseTable() {
const [data, setData] = useState([]);
const [CourseID, setCourseID] = useState(0);
useEffect(() => {
Axios
.get("http://localhost:3003/coursestable")
.then(result => setData(result.data));
}, []);
return (
<div className="main">
<h2>
<table className="table" >
<thead className="thead-dark">
<tr>
<th scope="col">Course Number</th>
<th scope="col">Course Name</th>
<th scope="col">View Details</th>
<th scope="col">Edit Course</th>
<th scope="col">Delete Course</th>
</tr>
</thead>
<tbody>
{data.map((item, id) => {
return <tr key={id}>
{localStorage.setItem('CourseID', item.CourseID)}
<td>{item.CourseID}</td>
<td>{item.Name}</td>
<td>View</td>
<td><a href={`/editcourse2`} className="btn btn-primary" >
Edit</a></td>
<td><button className="btn btn-primary">Delete</button></td>
</tr>
})}
</tbody>
</table>
</h2>
</div>
)
}
export default EditCourseTable;
I use the localStorage to store the CourseId that the user click on (when click in Edit or View), but it is store the last courseID in the table, not the courseID that I click on. Whats the error?
You should insert the value in the localStorage by triggering a function called on click of an element
function storeCourse(id) {
localStorage.setItem('CourseID', id)
}
<td>
<span
className="btn btn-primary"
onClick={() => storeCourse(item.CourseID)}>
View
</span>
</td>
You need to create something to catch that click, so you can create some function like
const handleClickItem = (courseID) => {
localStorage.setItem('CourseID', courseID)
}
So whenever the user click, it will use onClick, so you can pass something like onClick = { () => handleClickItem(item.CourseID)} then pass the item.CourseID into that handleClickItem
Now the handleClickItem has the courseID
That's when you localStorage.setItem('CourseID', item.CourseID)
function EditCourseTable() {
const [data, setData] = useState([]);
const [CourseID, setCourseID] = useState(0);
useEffect(() => {
Axios
.get("http://localhost:3003/coursestable")
.then(result => setData(result.data));
}, []);
//- Add handleClickItem
const handleClickItem = (courseID) => {
localStorage.setItem('CourseID', courseID)
}
return
Inside the return, the map one, just add onClick where ever you want the user to click
for example:
<tr key={id} onClick = {() => handleClickItem(item.CourseID)}>
Your localStorage code runs when rendered so the last rendered item's id is saved to localStorage. You should use the function onClick.
<tbody>
{data.map((item, id) => {
return <tr key={id} onClick={() => localStorage.setItem('CourseID', item.CourseID)}>
<td>{item.CourseID}</td>
<td>{item.Name}</td>
<td>View</td>
<td><a href={`/editcourse2`} className="btn btn-primary" >
Edit</a></td>
<td><button className="btn btn-primary">Delete</button></td>
</tr>
})}
</tbody>
In your code you save data to local storage on items render. All items saves to local storage on key CourseID in render order.
Because of this after items render local storage CourseID value equal last item in rendered collection.
Right chose for solving this problem is saving data to local storage on link click.
But i think you does not need saving this data to local storage. React allow storing this in state.
Example for your code:
const [clickedCourseId, setClickedCourseId] = useState(null);
...
render (
...
{data.map((item, id) => {
return (
<tr key={id}>
<td>{item.CourseID}</td>
<td>{item.Name}</td>
<td>View</td>
<td><a href={`/editcourse2`} onClick={() => { setClickedCourseId(item.CourseID) }} className="btn btn-primary" >
Edit</a></td>
<td><button className="btn btn-primary">Delete</button></td>
</tr>
)
})}
In this example, when you click on View or Edit links, clickedCourseId being filled clicked item CourseId and you does not need to store it in localStorage.
However, if you want to store it in localStorage, you can change setClickedCourseId to your localStorage setItem
I'm trying to pass product data from AllProducts component to Product component.
AllProducts.jsx: is showing all the products I have and Product.jsx will show specific product and how can I pass data to Product.jsx?
Here is my AllProducts.jsx:
const AllProducts = (props) => {
const [products, setProducts] = useState([]);
const getProductsAPI = () => {
axios
.get("http://localhost:8000/api/products")
.then((res) => {
setProducts(res.data);
getProductsAPI();
})
.catch((err) => {
console.log(err);
});
};
useEffect(() => {
getProductsAPI();
}, [props]);
return (
<div>
<table className="table table-bordered table-hover">
<thead>
<tr>
<th>#</th>
<th>Title</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{products.map((product, i) => (
<tr key={i}>
<th scope="row">{i}</th>
<td>{product.title}</td>
<td>
<Link to={`/products/${product._id}`}> View </Link>
</td>
</tr>
))}
</tbody>
</table>
</div>
);
};
and here is my Product.jsx:
const Product = (props) => {
return (
<div className="container">
<h4>{props.product.title}</h4>
</div>
);
};
export default Product;
Here is my project github if you want to look at all the code I have: https://github.com/nathannewyen/full-mern/tree/master/product-manager
If the data is fully loaded for each product in AllProducts, and you don't want to make another API call by product id in the Product component, in this case, you don't have to use a route link to view Product, just make a conditional rendering to show Product component inside AllProducts component. pseudo-code as below,
const [showProduct, setShowProduct] = useState(false);
const [currentProduct, setCurrentProduct] = useState();
const showProduct = (product) => {
setShowProduct(true);
setCurrentProduct(product);
}
<tbody>
{products.map((product, i) => (
<tr key={i}>
<th scope="row">{i}</th>
<td>{product.title}</td>
<td>
<button type="button" onclick = {showProduct(product)}>View</button>
</td>
</tr>
))}
</tbody>
return (showProduct ? <Product /> : <AllProucts/>)
If you also need to make another API call to get extra data for each product, then use the router link but perhaps you can not pass props.
I'm working on a web-application using the MERN stack that displays a table of clients with their name, email, and phone number. I haven't implemented Redux quite yet, but I'm using 'uuid' to supplement data in the table until I can get the redux store set up. So far I have displaying the the list and adding a client to the list working fine, but I am having trouble with the pesky delete button.
This is the current ClientTable component
import React, { Component } from "react";
import { Table, Container, Button } from "reactstrap";
import { connect } from "react-redux";
import {
getClients,
addClient,
editClient,
deleteClient,
} from "../actions/clientActions";
import PropTypes from "prop-types";
const renderClient = (clients, index, id) => {
return (
<tr key={index}>
<td>
<Button
className="remove-btn"
color="danger"
size="sm"
onClick={() => {
this.setState((state) => ({
clients: state.clients.filter((client) => client.id !== id),
}));
}}
>
×
</Button>
</td>
<td>{clients.name}</td>
<td>{clients.email}</td>
<td>{clients.number}</td>
</tr>
);
};
class ClientTable extends Component {
componentDidMount() {
this.props.getClients();
}
onDeleteClick = (id) => {
this.props.deleteClient(id);
};
render() {
const { clients } = this.props.client;
// const { clients } = this.state;
return (
<Container id="listContainer">
<Table
id="listTable"
className="table-striped table-bordered table-hover"
dark
>
<tr class="listRow">
<thead id="tableHeader">
<tr>
<th id="listActions">Actions</th>
<th id="listName">Name</th>
<th id="listEmail">Email</th>
<th id="listNumber">Number</th>
</tr>
</thead>
<tbody class="listRow">{clients.map(renderClient)}</tbody>
</tr>
</Table>
</Container>
);
}
}
ClientTable.propTypes = {
getClients: PropTypes.func.isRequired,
client: PropTypes.object.isRequired,
};
const mapStateToProps = (state) => ({
client: state.client,
});
export default connect(mapStateToProps, {
getClients,
deleteClient,
addClient,
})(ClientTable);
This is the bit of code that is causing me issues
<Button
className="remove-btn"
color="danger"
size="sm"
onClick={() => {
this.setState((state) => ({
clients: state.clients.filter((client) => client.id !== id),
}));
}}
>
×
</Button>
When I click the "delete" button I keep getting TypeError: Cannot read property 'setState' of unedefined
I know the error is because of 'this' isn't bound to anything, but I'm uncertain how to bind it within an onClick event if that is even possible or what even to bind it to. I am just lost as to how to approach this problem. (I'm still quite new to React).
If anyone has any ideas it would be greatly appreciated!
move renderClient function to ClientTable, and use it as a method of this class.
class ClientTable extends Component {
componentDidMount() {
this.props.getClients();
}
renderClient = (clients, index) => {
return (
<tr key={index}>
<td>
<Button
className="remove-btn"
color="danger"
size="sm"
onClick={() => this.onDeleteClient(clients.id)}
>
×
</Button>
</td>
<td>{clients.name}</td>
<td>{clients.email}</td>
<td>{clients.number}</td>
</tr>
);
};
onDeleteClick = (id) => {
this.props.deleteClient(id);
};
render() {
const { clients } = this.props.client;
// const { clients } = this.state;
return (
<Container id="listContainer">
<Table
id="listTable"
className="table-striped table-bordered table-hover"
dark
>
<tr class="listRow">
<thead id="tableHeader">
<tr>
<th id="listActions">Actions</th>
<th id="listName">Name</th>
<th id="listEmail">Email</th>
<th id="listNumber">Number</th>
</tr>
</thead>
<tbody class="listRow">{clients.map(this.renderClient)}</tbody>
</tr>
</Table>
</Container>
);
}
}
I make request to server and I get response. Response it data which I display in view table-list. Also now I try implement when I click button changeAsc happen sort by asc-desc.
But I need that sort by asc-desc was happening when I click on header header id in table. And display the word asc or desc to the right of the header id. Table I export in file Home.js from file - Table.js.
What I need to change in file Table.js that implement sort when I click to header id?
Home.js:
import Table from "./Table/Table.js";
const Home = () => {
const [value, setValue] = useState({
listCategory: [],
sortAscDesc: "asc",
});
useEffect(() => {
async function fetchData(sortAscDesc) {
const res = await api('api/categories', sortAscDesc);
/....
}
fetchData(value.sortAscDesc);
}, [value.sortAscDesc]);
const changeSortAscDesc = () => {
setValue((prev) => ({
...prev,
sortAscDesc: prev.sortAscDesc == 'asc' ? 'desc' : 'asc'
}));
};
return (
<div>
<Table dataAttribute={value.listCategory}/>
// I WANT DELETE THIS BUTTON: - BECAUSE I WANT SORT BY HEADER "id"
<button onClick={() => changeSortAscDesc()}>changeAsc</button>
</div>
);
};
Table.js:
export default ({dataAttribute}) => (
<table className="table">
<thead className="table-head">
<tr>
<th>id</th> //I WANT SORT WHEN I CLICK ELEMENT id
<th>title</th>
<th>created_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>
</tr>
))}
</tbody>
</table>
);
You can try like this:
<Table dataAttribute={value.listCategory} changeSortAscDesc={changeSortAscDesc} />
In your Table.js
export default (props) => (
<table className="table">
<thead className="table-head">
<tr>
<th onClick={props.changeSortAscDesc}>id</th> //I want sort when I click by element id
<th>title</th>
<th>created_at</th>
</tr>
</thead>
<tbody>
{props.dataAttribute.map(item => (
<tr key={item.id}>
<td>{item.id}</td>
<td>{item.title}</td>
<td>{item.created_at}</td>
</tr>
))}
</tbody>
</table>
);