i am trying to sort html table by ASC and desc order but this table is not working properly its working only for first column when i put id name can you please help me for make this work sorting func by ASC and Desc. this is my code so far i tried but its not working Thanks
import React from "react";
class Th extends React.Component {
handleClick = () => {
const { onClick, id } = this.props;
onClick(id);
};
render() {
const { value } = this.props;
return <th onClick={this.handleClick}>{value}</th>;
}
}
class App extends React.Component {
state = {
users: []
};
async componentDidMount() {
const res = await fetch(
`https://run.mocky.io/v3/6982a190-6166-402e-905f-139aef40e6ef`
);
const users = await res.json();
this.setState({
users
});
}
handleSort = id => {
this.setState(prev => {
return {
[id]: !prev[id],
users: prev.users.sort((a, b) =>
prev[id] ? a[id] < b[id] : a[id] > b[id]
)
};
});
};
render() {
const { users } = this.state;
return (
<table>
<thead>
<tr>
<Th onClick={this.handleSort} id="mileage" value="Mileage" />
<Th onClick={this.handleSort} id="overall_score" value="Overall score" />
<Th onClick={this.handleSort} id="fuel_consumed" value="Fuel Consumed" />
</tr>
</thead>
<tbody>
{users.map(user => (
<tr>
<td>{user.span.mileage.value}</td>
<td>{user.span.overall_score.value}</td>
<td>{user.span.fuel_consumed.value}</td>
</tr>
))}
</tbody>
</table>
);
}
}
export default App;
To make it works you need to change a few thigs:
the setState merges new data with old one, so [id]: !prev[id] adds new property to state for each column you filter without removing old one. It's better to store column to filter in dedicated state property (e.g. sortBy).
fix sorting function to make it sorting the users by correct object properties
remove async from componentDidMount and change fetch to use then/catch instead of async/await (it makes your code more React-ish).
Use example below as an inspiration:
class App extends React.Component {
state = {
sortBy: null,
order: "ASC",
users: []
};
componentDidMount() {
fetch(`https://run.mocky.io/v3/6982a190-6166-402e-905f-139aef40e6ef`)
.then(response => response.json())
.then(users => this.setState({users}))
.catch(err => console.log('Error', err));
}
handleSort = id => {
this.setState(prev => {
const ordered = prev.users.sort((a, b) =>
prev.order === "ASC"
? a["span"][id]["value"] < b["span"][id]["value"]
: a["span"][id]["value"] > b["span"][id]["value"]
);
return {
sortBy: id,
order: prev.order === "ASC" ? "DESC" : "ASC",
users: ordered
};
});
};
render() {
const { users } = this.state;
return (
<table>
<thead>
<tr>
<Th onClick={this.handleSort} id="mileage" value="Mileage" />
<Th
onClick={this.handleSort}
id="overall_score"
value="Overall score"
/>
<Th
onClick={this.handleSort}
id="fuel_consumed"
value="Fuel Consumed"
/>
</tr>
</thead>
<tbody>
{users.map(user => (
<tr>
<td>{user.span.mileage.value}</td>
<td>{user.span.overall_score.value}</td>
<td>{user.span.fuel_consumed.value}</td>
</tr>
))}
</tbody>
</table>
);
}
}
class Th extends React.Component {
handleClick = () => {
const { onClick, id } = this.props;
onClick(id);
};
render() {
const { value } = this.props;
return <th onClick={this.handleClick}>{value}</th>;
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<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>
<div id="root"></div>
Keep in mind that in only works with the current data schema and fields you already have. If you want to change the fields to sort by you need to update sorting function.
Related
The code that I posted below is the API request from which I make a table. This table has 4 columns: id, userid, title. I want to understand how I can sort by userid and title, as shown in the photo. It would be great if the steps were described in detail.
I'm trying to group the tab as shown in the photo, but I can't.
Can you suggest/show me how to do this?
Also wanted to know how to reset the group value of a column?
I will be grateful for any help.
My code:
import React from "react";
import "./GroupByUserID.css";
import { Link } from "react-router-dom";
export default class GroupByUserID extends React.Component {
// Constructor
constructor(props) {
super(props);
this.state = {
items: [],
};
}
componentDidMount = () => {
this.apiFetch();
};
//Fetch data from API
apiFetch = () => {
return fetch("https://jsonplaceholder.typicode.com/todos")
.then((res) => res.json())
.then((json) => {
this.setState((prevState) => {
return { ...prevState, items: json };
});
});
};
// Sort UserID
setSortedItemsUserID = () => {
const { items } = this.state;
const sortedUserID = items.sort((a, b) => {
if (a.userId < b.userId) {
return items.direction === "ascending" ? -1 : 1;
}
if (a.userId > b.userId) {
return items.direction === "ascending" ? 1 : -1;
}
return 0;
});
console.log(sortedUserID);
this.setState((prevState) => {
return { ...prevState, items: sortedUserID };
});
};
render() {
const { items } = this.state;
return (
<div>
<h1>Home Page</h1>
<table>
<thead>
<tr>
<th>
<Link target="self" to="/">
View Normal
</Link>
</th>
<th>Group By UserID</th>
</tr>
</thead>
<thead>
<tr>
<th>
User ID
<button
type="button"
onClick={() => this.setSortedItemsUserID()}
>
⬇️
</button>
</th>
<th>Title</th>
</tr>
</thead>
<tbody>
{items.map((item) => (
<tr key={item.userId + item.title}>
<td>{item.userId}</td>
<td>{item.title}</td>
</tr>
))}
</tbody>
</table>
</div>
);
}
}
I am trying to parse the data dynamically for the table. So far I have tried the following to display the table.
renderTableData = () => {
return this.props.data.map((item, index) => {
const { name, value } = item;
return (
<tr key={index}>
<td>{name}</td>
<td>{value}</td>
</tr>
);
});
};
Here I am hardcoding the field values for displaying. I need this to be dynamic
Full code: https://codesandbox.io/s/react-basic-class-component-3kpp5?file=/src/Table.js:0-805
import * as React from "react";
class Table extends React.Component {
renderTableData = () => {
return this.props.data.map((item, index) => {
const { name, value } = item;
return (
<tr key={index}>
<td>{name}</td>
<td>{value}</td>
</tr>
);
});
};
renderTableHeader = () => {
let header = Object.keys(this.props.data[0]);
return header.map((key, index) => {
return <th key={index}>{key.toUpperCase()}</th>;
});
};
render() {
return (
<div>
<table>
<tbody>
<tr>{this.renderTableHeader()}</tr>
{this.renderTableData()}
</tbody>
</table>
</div>
);
}
}
export default Table;
You can loop through object properties with Object.entries
renderTableData = () => {
return this.props.data.map((item, index) => {
return (
<tr key={index}>
{Object.entries(item).map(([key, value])=> <td key={key}>{value}</td>)}
</tr>
);
});
};
However, as you can see you lost control of the order of columns. Additionaly there might be columns you don't wish to display.
You can tackle that by appending Object.entries with custom implemented functions
<tr key={index}>
{Object.entries(item)
.filter(predicateFunction)
.sort(sortingFunction).map(([key, value])=> <td key={key}>{value}</td>)}
</tr>
Or switch to react-data-table
I have app wrote on pure React where I make request to server and get response - category list. This list I can sort by asc-desc when I click by title table id.I needed to remake small part of my React app to Redux.
But when I remake this part to redux I have error:
Cannot read property 'sortAscDesc' of undefined - in reducer.
Also error in Table.js in line:
<th className="th-id" onClick={() => dispatch(changeSortAscDesc())}>ID <small>{sortAscDesc}</small></th>
First in my question I'll write code that I remake to Redux
and below after _______________________________ I'll write small part my app which wrote on pure React(before remake to redux) and work well.
Wrote on REDUX:
filterList.js(action):
export const changeSortAscDesc = (prev) => ({
type: "SORT_ASC_DESC",
payload: prev
});
filterList.js(reducer):
const initialState = {
sortAscDesc: "asc",
};
export function filterList(state = initialState, action) {
switch (action.type) {
case "SORT_ASC_DESC": {
const { payload } = action;
return {
...state,
sortAscDesc: payload.sortAscDesc == 'asc' ? 'desc' : 'asc'
};
}
default:
return state;
}
}
Table.js:
export default (props) => {
const sortAscDesc = useSelector(state => state.filterListReducer.sortAscDesc);
const dispatch = useDispatch();
return (
<table>
<thead>
<tr>
<th></th>
<th onClick={() => dispatch(changeSortAscDesc())}>ID <small>{sortAscDesc}</small></th>
<th>TITLE</th>
</tr>
</thead>
<tbody className="table-body">
{props.dataAttribute.map(item => (
<tr key={item.id}>
<td>{item.id} </td>
<td>{item.title} </td>
</tr>
))}
</tbody>
</table>
);}
_______________________________________________________
Wrote on pure React (before remake to redux):
Home.js:
const Home = () => {
const [value, setValue] = useState({
listCategory: [],
sortAscDesc: "asc",
});
// Here useEffect and fetch function, but I dont write it, because it not related with my question
const changeSortAscDesc = () => {
setValue((prev) => ({
...prev,
sortAscDesc: prev.sortAscDesc == 'asc' ? 'desc' : 'asc'
}));
};
return (
<div>
<Table dataAttribute={value.listCategory}
changeSortAscDesc={changeSortAscDesc}
sortAscDesc={value.sortAscDesc}
/>
</div>
);
Table.js:
export default (props) => {
const sortAscDesc = useSelector(state => state.filterListReducer.sortAscDesc);
const dispatch = useDispatch();
return (
<table>
<thead>
<tr>
<th></th>
<th onClick={props.changeSortAscDesc}>ID <small>{props.sortAscDesc}</small></th>
<th>TITLE</th>
</tr>
</thead>
<tbody className="table-body">
{props.dataAttribute.map(item => (
<tr key={item.id}>
<td>{item.id} </td>
<td>{item.title} </td>
</tr>
))}
</tbody>
</table>
);}
You are not dispatching any payload with your action -
<th onClick={() => dispatch(changeSortAscDesc(dataThatNeedsToBePassed))}>ID <small>{sortAscDesc}</small></th> //pass data as parameter
EDIT- You can make it work in this way -
const initialState = {
sortAscDesc: "asc",
};
export function filterList(state = initialState, action) {
switch (action.type) {
case "SORT_ASC_DESC": {
const { payload } = action; // no need //
return {
...state,
sortAscDesc: state.sortAscDesc == 'asc' ? 'desc' : 'asc'
};
}
default:
return state;
}
}
And you can remove payload from your action -
export const changeSortAscDesc = () => ({
type: "SORT_ASC_DESC",
payload: prev// no need //
});
I have created a component that sorts and filters an HTML table. The functionality is correct but I have a problem where my table renders "No asset records found." but when I click on one of the headers it displays the contents of the data array in state. I am truly stuck and confused on this strange behaviour. I think the problem might be with the filterAssets function because if I change from this:
let filterAssets = this.state.data.filter(a => {
return a.name.toLowerCase().indexOf(this.state.search) !== -1
})
to this:
let filterAssets = this.props.assetManagement.filter(a => {
return a.name.toLowerCase().indexOf(this.state.search) !== -1
})
Here is the code below if it helps
import React, { Component, Fragment } from 'react'
import { connect } from 'react-redux'
import PropTypes from 'prop-types'
import { getAssetManagement } from '../../actions/asset-management'
class AssetManagement extends Component {
static propTypes = {
assetManagement: PropTypes.array.isRequired,
getAssetManagement: PropTypes.func.isRequired
}
componentDidMount() {
this.props.getAssetManagement()
}
state = {
name: '',
search: '',
data: []
}
sortBy = this.sortBy.bind(this)
compareBy = this.compareBy.bind(this)
onSubmit = e => {
e.preventDefault()
}
onChange = e =>
this.setState({
[e.target.name]: e.target.value
})
updateSearch = e =>
this.setState({
search: e.target.value.substr(0, 20)
})
compareBy(key) {
return (a, b) => {
if (a[key] < b[key]) return -1
if (a[key] > b[key]) return 1
return 0
}
}
sortBy(key) {
let arrayCopy = [...this.props.assetManagement]
this.state.data.sort(this.compareBy(key))
this.setState({ data: arrayCopy })
}
render() {
let filterAssets = this.state.data.filter(a => {
return a.name.toLowerCase().indexOf(this.state.search) !== -1
})
return (
<Fragment>
{/* Search input */}
<div class="input-group mb-1">
<div class="input-group-prepend">
<span class="input-group-text btn-secondary">
<i class="fas fa-search" />
</span>
</div>
<input
className="form-control"
type="text"
placeholder="Search Asset"
onChange={this.updateSearch.bind(this)}
value={this.state.search}
/>
</div>
{/* Asset management table */}
<div className="table-responsive">
<table className="table table-bordered text-center">
<thead>
<tr>
<th onClick={() => this.sortBy('id')}>ID</th>
<th onClick={() => this.sortBy('name')}>Name</th>
</tr>
</thead>
<tbody>
{filterAssets != 0 ? (
filterAssets.map(a => (
<tr key={a.id}>
<td>{a.id}</td>
<td>{a.name}</td>
</tr>
))
) : (
<tr>
<td colSpan={6}>No asset records found.</td>
</tr>
)}
</tbody>
</table>
</div>
</Fragment>
)
}
}
const mapStateToProps = state => ({
assetManagement: state.assetManagement.assetManagement
})
export default connect(
mapStateToProps,
{ getAssetManagement }
)(AssetManagement)
Change filterAssets != 0 to filterAssets.length > 0
One first render:
let filterAssets = this.state.data.filter(a => {
return a.name.toLowerCase().indexOf(this.state.search) !== -1
})
Your this.state.data is empty, only this.props.assetManagement available if you handle redux properly so no wonder it you cannot get anything from filtering.
Btw: filterAssets != 0 is absolutely wrong, so go ahead and change this line first.
When you use the alternative syntax for a React Component without using a constructor you no longer have access to props. So if you go back to using a standard constructor the problem disappears, e.g.:
constructor(props) {
super(props);
this.state = {
name: "",
search: "",
data: this.props.assetManagement
};
this.sortBy = this.sortBy.bind(this);
this.compareBy = this.compareBy.bind(this);
}
The real problem you have here is that you have two source of data: state.data and props.assetManagement - you retrieve from redux and get newest data from props.assetManagement, but when you need to trigger sorting, you make a copy to state.data. Then problem arises since you don't copy from props.assetManagement to state.data until you trigger sortBy function.
A solution for that is to get rid of state.data and store the sorting key in state. You can update, reset that key value, and sorting logic should be apply to props.assetManagement only:
class AssetManagement extends Component {
static propTypes = {
assetManagement: PropTypes.array.isRequired,
getAssetManagement: PropTypes.func.isRequired
}
componentDidMount() {
this.props.getAssetManagement()
}
state = {
name: '',
search: '',
sortingKey: ''
}
sortBy = this.sortBy.bind(this)
compareBy = this.compareBy.bind(this)
onSubmit = e => {
e.preventDefault()
}
onChange = e =>
this.setState({
[e.target.name]: e.target.value
})
updateSearch = e =>
this.setState({
search: e.target.value.substr(0, 20)
})
compareBy(key) {
return (a, b) => {
if (a[key] < b[key]) return -1
if (a[key] > b[key]) return 1
return 0
}
}
sortBy(key) {
if (key !== this.state.sortingKey) {
this.setState({ sortingKey: key });
}
}
render() {
let sortAssets = !!this.state.sortingKey ?
this.props.assetManagement.sort(this.compareBy(this.state.sortingKey)) :
this.props.assetManagement;
let filterAssets = sortAssets.filter(a => {
return a.name.toLowerCase().indexOf(this.state.search) !== -1
});
return (
<Fragment>
{/* Search input */}
<div class="input-group mb-1">
<div class="input-group-prepend">
<span class="input-group-text btn-secondary">
<i class="fas fa-search" />
</span>
</div>
<input
className="form-control"
type="text"
placeholder="Search Asset"
onChange={this.updateSearch.bind(this)}
value={this.state.search}
/>
</div>
{/* Asset management table */}
<div className="table-responsive">
<table className="table table-bordered text-center">
<thead>
<tr>
<th onClick={() => this.sortBy('id')}>ID</th>
<th onClick={() => this.sortBy('name')}>Name</th>
</tr>
</thead>
<tbody>
{filterAssets != 0 ? (
filterAssets.map(a => (
<tr key={a.id}>
<td>{a.id}</td>
<td>{a.name}</td>
</tr>
))
) : (
<tr>
<td colSpan={6}>No asset records found.</td>
</tr>
)}
</tbody>
</table>
</div>
</Fragment>
)
}
}
Sample code: https://codesandbox.io/s/n91pq7073l
I have a function called handleDelete that takes in a movie object and will filter based on which movie is passed. When I click it, I get the error Cannot read property 'filter' of undefined.
Update: Posted all of the code.
JS
handleDelete = (movie) => {
const movies = this.setState.movies.filter(m => m._id !== movie._id);
this.setState({ movies });
};
JSX
import React, { Component } from "react";
import "../services/fakeMovieService";
import { getMovies } from "../services/fakeMovieService";
class MovieTable extends Component {
state = {
movies: getMovies()
};
render() {
return (<table className="table">
<thead>
<tr>
<th scope="col">Title</th>
<th scope="col">Genre</th>
<th scope="col">Stock</th>
<th scope="col">Rate</th>
<th> </th>
</tr>
</thead>
<tbody>
{this.state.movies.map(movies => (
<tr key={movies._id}>
<td> {movies.title} </td>
<td> {movies.genre.name}</td>
<td> {movies.numberInStock} </td>
<td> {movies.dailyRentalRate}</td>
<td
onClick={movie => this.handleDelete(movie)}
className="btn btn-danger btn-sm"
>
Delete
</td>
</tr>
))}
</tbody>
</table>);
}}
export default MovieTable;
Movies array
const movies = [ {
_id: "5b21ca3eeb7f6fbccd471815",
title: "Terminator",
genre: { _id: "5b21ca3eeb7f6fbccd471818", name: "Action" },
numberInStock: 6,
dailyRentalRate: 2.5,
publishDate: "2018-01-03T19:04:28.809Z"
}];
It looks like you made a typo this.setState.movies.filter should be this.state.movies.filter so :
handleDelete = movie => {
const movies = this.state.movies.filter(m => m._id !== movie._id);
this.setState({ movies });
};
Without more context I'm assuming your issue is how you a referencing handleDelete. My primary assumption is that you aren't binding handleDelete to this in the constructor when using a smart component.
class MyComp extends React.Component {
constructor(props) {
super(props);
this.state = {
movies: null,
};
this.handleDelete = this.handleDelete.bind(this);
}
handleDelete = movie => {
const movies = this.setState.movies.filter(m => m._id !== movie._id);
this.setState({ movies });
}
render() {
return (<td
onClick={movie => this.handleDelete(this.props.movie)}
className="btn btn-danger btn-sm">
Delete
</td>);
}
}