No access to "this" - reactjs

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>
);
}
}

Related

Is it possible to dipatsch on useSelector function?

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>
</>
);
};

How to delete item seleted in table product

I am trying to delete a product, but it's doesn't show success. I do not know how to get the id of that product to delete
My button onClick = {handleDelete} is import from component in other folder. I try to create handleDelete function, but I missing something in this case.
This is my code for that section
import React, { useState, useEffect } from "react";
import { Container, Row, Col, Table } from "react-bootstrap";
import Loading from "../../components/Loading";
import Button from "../../components/Button/index"
import firebaseApp from "../../api/config";
const ProductTableList = ({
products,
loading,
fetchProductRequest
}) => {
useEffect(() => {
fetchProductRequest();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const firebaseDb = firebaseApp.database();
const [currentId, setCurrentId] = useState("");
if (loading) {
return (
<Container>
<Row>
<Col>
<Loading />
</Col>
</Row>
</Container>
);
}
const handleDelete = (id) => {
const productId = firebaseDb.ref().push().key;
if (window.confirm("Are you sure to delete this record?")) {
firebaseDb
.ref("products")
.child(`products/${productId}`)
.remove((err) => {
if (err) console.log(err);
else setCurrentId("");
});
}
}
const handleUpdate = (event) => {
//TODO
}
return (
<Table striped bordered hover className="product-table">
<thead>
<tr>
<th>No.</th>
<th className="image">Image</th>
<th>Name</th>
<th>Category</th>
<th>Price</th>
<th>Description</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{!!products && products.length > 0 ? (
products.map((product, index) => {
return (
<tr key={index}>
<td>{index}</td>
<td>{product.image}</td>
<td>{product.name}</td>
<td>{product.category}</td>
<td>{product.price}</td>
<td>{product.description}</td>
<td>
<Button onClick={handleDelete} btnText="Delete" />
<Button onClick={handleUpdate} btnText="Update" />
</td>
</tr>
);
})
) :
(
<tr><td className="center-title">Product list is empty!</td></tr>
)}
</tbody>
</Table>
)
}
export default ProductTableList;
Can anyone help me? How do I delete the product that I have selected
Can anyone explain or support for me why? Thank you so much
I made a example, you need to add your function on button click and use your item id to be removed.
import React, { useState, useEffect } from "react";
import { Table } from "react-bootstrap";
const ProductTableList = () => {
const [currentId, setCurrentId] = useState("");
const [products, setProducts] = useState([{
image: 'image',
name: '01',
category: '01',
price: '01',
description: '01'
},
{
image: 'image',
name: '02',
category: '02',
price: '02',
description: '02'
},
{
image: 'image',
name: '03',
category: '03',
price: '03',
description: '03'
}])
const handleDelete = (id) => {
const removeItem = products.filter((item) => item !== products[id])
setProducts(removeItem)
}
return (
<Table striped bordered hover className="product-table">
<thead>
<tr>
<th>No.</th>
<th className="image">Image</th>
<th>Name</th>
<th>Category</th>
<th>Price</th>
<th>Description</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{!!products && products.length > 0 ? (
products.map((product, index) => {
return (
<tr key={index}>
<td>{index}</td>
<td>{product.image}</td>
<td>{product.name}</td>
<td>{product.category}</td>
<td>{product.price}</td>
<td>{product.description}</td>
<td>
<button onClick={() => handleDelete(index)}>Delete</button>
</td>
</tr>
);
})
) :
(
<tr><td className="center-title">Product list is empty!</td></tr>
)}
</tbody>
</Table>
)
}
export default ProductTableList;
Also, avoid index as element key
{ items.map((item, index) => (<li key={index}>{item}</li>)) }
When a list item was added or removed, and the key kept the same, the React assumed that the DOM element had not changed, and the app could not render.
An alternative to cases that the list doesn't have a unique ID is to generate one using shortID.
https://www.npmjs.com/package/shortid

How can I pass props to another components with in reactjs

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.

Creating a component in react and typescript

I am trying to convert this table below from react to typescript. I want to make it universal table component where I can use for every data. This is a sample a table I created in react and its working well. In typescript, the class experts an interface which I have been finding it difficult to apply here.The table must be able to accept data from a webservice and display at any point in time. What am I supposed to add.
class Table extends Component {
render() {
const { characterData, removeCharacter } = this.props;
return (
<table className="table-bordered table-striped">
<TableHeader />
<TableBody characterData={characterData}
removeCharacter={removeCharacter}
/>
</table>
);
}
}
const TableHeader = () => {
return (
<thead>
<tr>
<th width="300">Name</th>
<th width="300">Job</th>
<th width="300">Date</th>
</tr>
</thead>
);
}
const TableBody = props => {
const rows = props.characterData.map((row, index) => {
return (
<tr key={index}>
<td >{row.name}</td>
<td >{row.job}</td>
<td >{row.date}</td>
<td><button onClick={() => props.removeCharacter(index)}>Delete</button></td>
</tr>
);
});
return <tbody>{rows}</tbody>;
}
export default Table;
You can type check your props by adding chevrons next to the Component prototype. The first argument is type checking your props and the second argument will type check your internal state.
interface TableProps {
characterData: //...however the data should be structured
removeCharacter: () => void;
}
interface TableState {
//... this is empty because you haven't defined any internal component state
}
class Table extends Component<TableProps, TableState> {
render() {
const { characterData, removeCharacter } = this.props;
return (
<table className="table-bordered table-striped">
<TableHeader />
<TableBody characterData={characterData}
removeCharacter={removeCharacter}
/>
</table>
);
}
}

React 'this' Context within Method

I am trying to call a Method on a Button click like this (the button is returned within a method itself):
<button onClick={removeClick}>Remove</button>
this is the method i am trying to call
removeClick = event => {
console.log('clicked');
};
im always getting errors like '_this2 is undefined'
i have already tried binding the method in the constructor.
an arrow function within the onClick didn't work as well
removeClick = event => {
console.log('clicked');
};
renderWeather(cityData) {
const name = cityData.city.name;
const temps = cityData.list.map(weather => weather.main.temp - 273.15);
const pressures = cityData.list.map(weather => weather.main.pressure);
const humidities = cityData.list.map(weather => weather.main.humidity);
const { lon, lat } = cityData.city.coord;
return (
<tr key={lon + lat}>
<td>
<GoogleMap lon={lon} lat={lat} />
<span className="city-name">{name}</span>
</td>
<td>
<WeatherChart data={temps} color="red" unit="°C" />
</td>
<td>
<WeatherChart data={pressures} color="orange" unit="hPA" />
</td>
<td>
<WeatherChart data={humidities} color="blue" unit="%" />
</td>
<td>
<button onClick={this.removeClick}>Remove</button>
</td>
</tr>
);
}
render() {
return (
<table className="table table-hover">
<thead>
<tr>
<th>City</th>
<th>Temperature (°C) </th>
<th>Pressure (hPA)</th>
<th>Humidity (%)</th>
<th />
</tr>
</thead>
<tbody>{this.props.weather.map(this.renderWeather)}</tbody>
</table>
);
}
}
const mapStateToProps = ({ weather }) => ({
weather
});
const mapDispatchToProps = {
removeCity
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(WeatherList);
this is how the my component looks like
I hope anyone can help me
try
renderWeather = cityData => {
...
}
and because you are using your clickhandler for a set of data you may want to pass some kind of identifier to it
removeClick = id => event => {
console.log(`Removing data for ${id}`)
}
and call it by
<button onClick={() => {this.removeClick(id)}}>Remove</button>
Try to pass this as a second param to map
<tbody>{this.props.weather.map(this.renderWeather, this)}</tbody>
class Item extends React.Component {
removeClick = event => {
console.log('clicked');
}
renderWeather(cityData) {
return <button onClick={this.removeClick}>Remove</button>
}
render() {
return (
<table className="table table-hover">
<thead>
<tr>
<th>City</th>
<th>Temperature (°C) </th>
<th>Pressure (hPA)</th>
<th>Humidity (%)</th>
<th />
</tr>
</thead>
<tbody>{this.props.weather.map(this.renderWeather, this)}</tbody>
</table>
)
}
}
const weather = [
{
city: {name: '123'}
}
];
ReactDOM.render(
<Item weather={weather} />,
document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id='root'></div>

Resources