Update a component list after deleting record - reactjs

I have a component. It displays a list of records. You can click the delete icon, and as soon as you go to a different page and return to the list, the record is no longer there. How do I remove the record from the list without going to a different page?
I've tried using componentWillUpdate() and componentDidUpdate() and placing my getTerritoryGeographies(this.props.params.id) in those functions, but those functions keep calling the data and do not stop. I'm restricted to API limits.
import React, { Component, PropTypes} from 'react';
import { Link } from 'react-router';
import { connect } from 'react-redux';
import { reduxForm } from 'redux-form';
import { getTerritoryGeographies, deleteTerritoryGeography } from '../actions/index';
import TerritoryTabs from './territory-tabs';
class TerritoryGeographyList extends Component {
componentWillMount() {
//console.log('this is the child props (TerritoryGeographyList)');
console.log(this.props);
this.props.getTerritoryGeographies(this.props.params.id);
}
componentDidMount() {
console.log('componentDidMount');
}
componentWillUpdate() {
console.log('componentWillUpdate');
this.props.getTerritoryGeographies(this.props.params.id);
}
componentDidUpdate() {
console.log('componentDidUpdate');
}
onDeleteClick(id) {
this.props.deleteTerritoryGeography(id);
}
static contextTypes = {
router: PropTypes.object
}
renderTerritoryGeographyList() {
return this.props.territoryGeographies.map((geography) => {
return (
<tr key={geography.Id}>
<th scope="row" data-label="Country">
<div className="slds-truncate">{geography.tpslead__Country__c}</div>
</th>
<td data-label="State/Provice">
<div className="slds-truncate">{geography.tpslead__State__c}</div>
</td>
<td data-label="Postal Start">
<div className="slds-truncate">{geography.tpslead__Zip_Start__c}</div>
</td>
<td data-label="Postal End">
<div className="slds-truncate">{geography.tpslead__Zip_End__c}</div>
</td>
<td className="slds-text-align--right" data-label="Action">
<button className="slds-button slds-button--icon" title="edit">
<svg className="slds-button__icon" aria-hidden="true">
<use xlinkHref={editIcon}></use>
</svg>
<span className="slds-assistive-text">Edit</span>
</button>
<button onClick={() => this.onDeleteClick(geography.Id)} className="slds-button slds-button--icon" title="delete" data-aljs="modal" data-aljs-show="PromptConfirmDelete">
<svg className="slds-button__icon" aria-hidden="true">
<use xlinkHref={deleteIcon}></use>
</svg>
<span className="slds-assistive-text">Delete</span>
</button>
</td>
</tr>
);
});
}
render() {
return (
<TerritoryTabs id={this.props.params.id} listTab="geography">
<Link to={"territory/" + this.props.params.id + "/geography/new"} className="slds-button slds-button--brand">
Add New Geography
</Link>
<table className="slds-table slds-table--bordered slds-table--cell-buffer slds-m-top--large">
<thead>
<tr className="slds-text-title--caps">
<th scope="col">
<div className="slds-truncate" title="Country">Country</div>
</th>
<th scope="col">
<div className="slds-truncate" title="State/Provice">State/Provice</div>
</th>
<th scope="col">
<div className="slds-truncate" title="Postal Start">Postal Start</div>
</th>
<th scope="col">
<div className="slds-truncate" title="Postal End">Postal End</div>
</th>
<th className="slds-text-align--right" scope="col">
<div className="slds-truncate" title="Action">Action</div>
</th>
</tr>
</thead>
<tbody>
{this.renderTerritoryGeographyList()}
</tbody>
</table>
</TerritoryTabs>
);
}
}
function mapStateToProps(state) {
//console.log(state);
return { territoryGeographies: state.territoryGeographies.all
};
}
export default connect(mapStateToProps, { getTerritoryGeographies, deleteTerritoryGeography })(TerritoryGeographyList);
UPDATE: I figured out how do remove it by updating my onDeleteClick(), but it seems unnecessarily heavy for a react app. Thoughts?
onDeleteClick(id) {
this.props.deleteTerritoryGeography(id);
var geographyIndex = this.props.territoryGeographies.findIndex(x => x.Id==id)
this.setState(state => {
this.props.territoryGeographies.splice(geographyIndex, 1);
return {territoryGeographies: this.props.territoryGeographies};
});
}

Please post your action and reducer so that we can see what you are doing on the Redux side.
If you are renderings a list from data that is in the Redux store, you can use React-Redux's connect Higher Order Function to wrap the component, thus enabling access to the store as component props. So that part looks correct.
When you are firing the action creator, you should be passing in the id of the data that you would like deleted, and in your reducer, something like this:
case 'DELETE_TERRITORY':
const territoryId = action.data;
return state.filter(territory => territory.id !== territoryId);
When the reducer returns the new, filtered array, your component should update with the list minus the territory you just deleted.

This code controls whether the deletion operation is performed correctly. Returns the state to the first state if the deletion operation fails
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
handleDelete = async productId => {
const originalProducts = this.state.products;
const products = this.state.products.filter(p => productId !== p.productId);
this.setState({ products });
try {
const result = await deleteProduct(productId);
if (result.status === 200) {
// codes goes here. for example send notification
}
}
catch (ex) {
if (ex.response && ex.response.status === 404) {
// codes goes here. for example send notification
}
this.setState({ products: originalProducts });
}
}
reactjs

Related

Cannot increment variable in React component

I am learning React and trying to call API for users using this component:
It works and I get users for page=1,
But, when I click on next button, the method next is triggered which should update page variable to '2' but it doesn't happen.
import React , {Component} from "react";
import Wrapper from "../components/Wrapper";
import axios from "axios";
import {User} from "../../classes/user";
import {Link} from "react-router-dom";
class Users extends Component {
state = {
users: []
}
page = 1
componentDidMount = async () => {
const response = await axios.get(`users?page=${this.page}`)
console.log(response)
this.setState({
users: response.data.data
})
}
next = async () => {
this.page++; // this never gets incremented
await this.componentDidMount();
}
render() {
return (
<Wrapper>
<div className="d-flex justify-content-between flex-wrap flex-md-no-wrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<div className="btn-toolbar mb-2 mb-md-0">
<Link to={'users/create'} className="btn btn-sm btn-outline-secondary">Add</Link>
</div>
</div>
<div className="table-responsive">
<table className="table table-striped table-sm">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Name</th>
<th scope="col">Email</th>
<th scope="col">Role</th>
<th scope="col">Action</th>
</tr>
</thead>
<tbody>
{this.state.users.map(
(user: User) => {
return (
<tr>
<td>{user.id}</td>
<td>{user.first_name} {user.last_name}</td>
<td>{user.email}</td>
<td>{user.role.name}</td>
<td>
<div className="btn-group mr-2">
Edit
Delete
</div>
</td>
</tr>
)
}
)}
</tbody>
</table>
</div>
<nav>
<ul className="pagination">
<li className="page-item">
Previous
</li>
<li className="page-item">
<a href="" className="page-link" onClick={this.next}>Next</a>
</li>
</ul>
</nav>
</Wrapper>
)
}
}
export default Users;
No matter what page is always equal to 1:
url: 'users?page=1' in console
Why page variabel never gets incremented?
Alternatively, as suggested :
state = {
users: [],
page: 1
}
componentDidMount = async () => {
const response = await axios.get(`users?page=${this.state.page}`)
console.log(response)
this.setState({
users: response.data.data
})
}
next = async () => {
//this.page++;
this.setState({
page: this.state.page + 1
})
await this.componentDidMount();
}
Also do not update page either...
You have to put the variable page inside the state object.
You can increment the variable page like that :
this.setState({
page: this.state.page + 1
})
The thing you're missing is State concept as #devcarme mentioned. Particularly, you need to store page counter as a State, not as a variable.
I will oversimplify for learning purpose:
Before React, when you click Next page button, you have to reload the whole page with a new URL. By only doing that, the page can have new content.
React don't need to do that. React can "react" to that change by storing update in a State. With State, React can bring new content without reloading the whole page.
If you're not learn for maintaining codebase, I suggest you code in Functional component as it is shorter than Class component version, which make it's easier to approach.
In the long run, React dev team will focus on Functional component and keep Class component for legacy codebase. They will provide ways to update, for example React Hooks, which makes functional component mostly equivalent to class component.
The solution was to use SyntheticEvent like this:
next = async (e:SyntheticEvent ) => {
e.preventDefault()
this.page++;
Now page gets incremented as I wanted.

Search data using axios in React using a Laravel API

I'm using React and Laravel to make an application. I managed to display data using Axios in a component, and managed to search data separatly in another component using this code :
const [data,setData]=useState([]);
async function search(key)
{
console.warn(key)
let result = await fetch("http://localhost:8000/api/search/"+key);
result = await result.json();
console.warn(result)
setData(result)
}
The problem is that I can't manage to combine between search and displaying data in a single component. How to do so ? I'm using a personal Laravel API.
JS Component of display data ( Without search ):
import React, { Component } from "react";
import axios from "axios";
import { Container, Dropdown, ListGroup, Button } from "react-bootstrap";
import { Table, Thead, Tbody, Tr, Th, Td } from "react-super-responsive-table";
class Patient extends React.Component {
constructor(props) {
super(props);
this.state = {
patients: [],
};
}
componentDidMount() {
axios
.get("api/patients")
.then((response) => {
this.setState({ patients: response.data });
})
.catch((err) => console.log(err));
}
render() {
return (
<div>
<Container>
<div className="form-group">
<label htmlFor="exampleInputEmail1">Search</label>
<input
type="text"
className="form-control"
id=" "
placeholder="Search"
id="name"
/>
</div>
<Table className="table table-hover">
<Thead className="thead-light text-center">
<Tr>
<Th>ID</Th>
<Th>NAME</Th>
<Th>FIRST NAME</Th>
</Tr>
</Thead>
<Tbody className="text-center">
{this.state.patients.reverse().map((patient) => (
<Tr>
<Td>
<b>{patient.id}</b>
</Td>
<Td>
<b>{patient.firstname}</b>
</Td>
<Td>
<b>{patient.lastname}</b>
</Td>
</Tr>
))}
</Tbody>
</Table>
</Container>
);
</div>
);
}
}
export default Patient;
My Laravel Controller to search :
public function search($key)
{
return Patient::where('name','Like',"%$key%")->get();
}
Laravel Route :
Route::get('/search/{key}/', [PatientController::class, 'search']);

No access to "this"

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

Redux: using different reducers for a component

I've got a component UsersList which I'd like to reuse with two different reducers - one for listing regular users (state.users.x) and one for listing administrators (state.adminusers.x). The display is the same in both cases, but the state is in different places and different api actions apply (different endpoints with different business rules).
How can I write my component so it can use either reducer?
Write the UsersList component as normal, but do not connect it to redux.
For example:
import React, { Component } from 'react';
import { Table } from 'react-bootstrap';
import UserInviteRow from 'jsx/components/Lib/Users/UserInviteRow';
export class UsersList extends Component {
render() {
const { inviteUserToOrg } = this.props;
return (
<Table bordered hover>
<thead>
<tr>
<th className="width-200">First Name</th>
<th className="width-250">Last Name</th>
<th>Email</th>
<th className="width-150">Last Login</th>
<th className="width-100"> </th>
</tr>
</thead>
<tbody>
<UserInviteRow invitefxn={ inviteUserToOrg }/>
{ this.renderRows() }
</tbody>
</Table>
);
}
renderRows() {
const { usersList } = this.props;
if( ! usersList.length ) {
return (
<tr>
<td colSpan="5">
<em>No users exist for this non-profit</em>
</td>
</tr>
);
}
return usersList.map( (user) => {
return (
<tr key={user.key}>
<td>{user.firstName}</td>
<td>{user.lastName}</td>
<td>{user.correspondenceEmailAddress}</td>
<td>{ (user.lastSeen) ? formatTime(user.lastSeen) : '' }</td>
<td className="text-center">
{ this.renderRemoveButton( user ) }
</td>
</tr>
);
});
}
renderRemoveButton(user) {
const { currentUser } = this.props;
if( currentUser.key === user.key ) {
// users cannot remove themselves
return null;
}
return (
<a className="text-danger" onClick={ () => { this.removeUser(user) } }>
<em className="fa fa-times" />
</a>
);
}
removeUser( user ) {
this.props.removeUserFromOrg(user.key);
}
}
export default UsersList;
Make sure both your reducers implement the action functions you use, in this case inviteUserToOrg and removeUserFromOrg.
Create new container components connected to each reducer
For example:
import { connect } from 'react-redux';
import {
inviteUserToOrg,
removeUserFromOrg
} as actions from 'jsx/redux/modules/nonadminUsers';
import UsersList from 'jsx/components/Lib/Users/UsersList';
var NonadminUserList = connect(
state => {
return {
usersList: state.users.usersList,
};
},
actions
)(UsersList);
export default NonadminUserList;
and
import { connect } from 'react-redux';
import {
inviteUserToOrg,
removeUserFromOrg
} as actions from 'jsx/redux/modules/adminUsers';
import UsersList from 'jsx/components/Lib/Users/UsersList';
var AdminUserList = connect(
state => {
return {
usersList: state.adminusers.usersList,
};
},
actions
)(UsersList);
export default AdminUserList;
Now changes to your presentation component, UsersList, will affect both container components and each container component can reference it's own reducer state and actions.

React JS assign separate onclick event to every row of table

I have a table which have a setting icon as the last column and whenever a user clicks on it, it should pop open a setting menu. To toggle between active class I used state and passed it to the array.map function, but what is happening is whenever a user clicks on one setting icon all the menus open simultaneously and the reason is they all have same click event and same state variable. How can I change it to where only the clicked setting icon should have its menu opened? My code is given below.
import React, { Component, PropTypes } from 'react';
import '../../../assets/custom_css/tables/unstackable_very_basic_striped_users_table.css';
import { v4 } from 'node-uuid';
import Language from '../../../assets/language';
class UnstackableVeryBasicStripedUsersTable extends Component {
static propTypes = {
rows: PropTypes.array.isRequired
};
constructor(props) {
super(props);
this.getTableRows = this.getTableRows.bind(this);
this.open_setting_menu = this.open_setting_menu.bind(this);
this.state = {
is_action_menu_active: false
};
}
getTableRows() {
const { rows } = this.props;
return rows.map(row => {
let drop_down_class = (this.state.is_action_menu_active) ? "active" : "";
let menu_class = (this.state.is_action_menu_active) ? "transition visible" : "";
return <tr key={v4()}>
{row.map(info => {
return <td key={v4()}>
{info}
</td>
})}
<td>
<div className={"ui right pointing dropdown icon " + drop_down_class} onClick={this.open_setting_menu}>
<i className="setting icon"/>
<div className={"menu " + menu_class}>
<div className="item">Edit</div>
<div className="item">Delete</div>
</div>
</div>
</td>
</tr>
});
}
open_setting_menu() {
this.setState({
is_action_menu_active: !this.state.is_action_menu_active
});
}
render() {
return <table className="ui unstackable celled very basic striped table unstackable_very_basic_striped_table">
<thead>
<tr>
<th>{Language.name}</th>
<th>{Language.role}</th>
<th>{Language.department}</th>
<th>{Language.action}</th>
</tr>
</thead>
<tbody>
{this.getTableRows()}
</tbody>
</table>
}
}
export default UnstackableVeryBasicStripedUsersTable;
If you want to use a single component, you can save the index of the selected row in the state:
import React, { Component, PropTypes } from 'react';
import '../../../assets/custom_css/tables/unstackable_very_basic_striped_users_table.css';
import { v4 } from 'node-uuid';
import Language from '../../../assets/language';
class UnstackableVeryBasicStripedUsersTable extends Component {
static propTypes = {
rows: PropTypes.array.isRequired
};
constructor(props) {
super(props);
this.getTableRows = this.getTableRows.bind(this);
this.open_setting_menu = this.open_setting_menu.bind(this);
this.state = {
selected_row_index: 0,
is_action_menu_active: false
};
}
getTableRows() {
const { rows } = this.props;
return rows.map((row, index) => {
let drop_down_class = (this.state.is_action_menu_active && this.state.selected_row_index === index) ? "active" : "";
let menu_class = (this.state.is_action_menu_active && this.state.selected_row_index === index) ? "transition visible" : "";
return <tr key={v4()}>
{row.map(info => {
return <td key={v4()}>
{info}
</td>
})}
<td>
<div className={"ui right pointing dropdown icon " + drop_down_class} onClick={() => this.open_setting_menu(index)}>
<i className="setting icon"/>
<div className={"menu " + menu_class}>
<div className="item">Edit</div>
<div className="item">Delete</div>
</div>
</div>
</td>
</tr>
});
}
open_setting_menu(index) {
this.setState({
is_action_menu_active: !this.state.is_action_menu_active,
selected_row_index: index
});
}
render() {
return <table className="ui unstackable celled very basic striped table unstackable_very_basic_striped_table">
<thead>
<tr>
<th>{Language.name}</th>
<th>{Language.role}</th>
<th>{Language.department}</th>
<th>{Language.action}</th>
</tr>
</thead>
<tbody>
{this.getTableRows()}
</tbody>
</table>
}
}
export default UnstackableVeryBasicStripedUsersTable;

Resources