Related
Hi Everyone, I'm trying to achieve adding edit and delete button in ReactJS using Mui Datatable, but the problem is that it keeps on repeating because of the Map sorry I'm just new in ReactJS anyways, here is my image and my code:
This is an example of my image:
And This My Code:
import React, { Component } from "react";
import { Link } from "react-router-dom";
import axios from "axios";
import MUIDataTable from "mui-datatables";
const Client = (props) => (
<>
<Link to={"client/edit/" + props.client._id} className="btn btn-primary">
Edit
</Link>
<a
href="client"
onClick={() => {
props.deleteClient(props.client._id);
}}
className="btn btn-danger"
>
Delete
</a>
</>
);
export default class ClientsList extends Component {
constructor(props) {
super(props);
this.deleteClient = this.deleteClient.bind(this);
this.state = { clients: [] };
}
componentDidMount() {
axios
.get("http://localhost:5000/clients/")
.then((response) => {
this.setState({ clients: response.data });
})
.catch((error) => {
console.log(error);
});
}
deleteClient(id) {
axios.delete("http://localhost:5000/clients/" + id).then((response) => {
console.log(response.data);
});
this.setState({
clients: this.state.clients.filter((el) => el._id !== id),
});
}
// This is the map I was talking about:
clientList() {
return this.state.clients.map((currentclient) => {
return (
<Client
client={currentclient}
deleteClient={this.deleteClient}
key={currentclient._id}
/>
);
});
}
render() {
const columns = [
{
name: "name",
label: "Name",
options: {
filter: true,
sort: true,
},
},
{
name: "address",
label: "Address",
options: {
filter: true,
sort: true,
},
},
{
name: "mobile",
label: "Mobile",
options: {
filter: true,
sort: true,
},
},
{
name: "email",
label: "Email",
options: {
filter: true,
sort: true,
},
},
{
name: "gender",
label: "Gender",
options: {
filter: true,
sort: true,
},
},
{
name: "birthday",
label: "Birthday",
options: {
filter: true,
sort: true,
},
},
{
name: "facebookPage",
label: "Facebook Page",
options: {
filter: true,
sort: true,
},
},
{
name: "facebookName",
label: "Facebook Name",
options: {
filter: true,
sort: true,
},
},
{
name: "existing",
label: "Existing",
options: {
filter: true,
sort: true,
},
},
{
name: "remarks",
label: "Remarks",
options: {
filter: true,
sort: true,
},
},
{
name: "Action",
options: {
customBodyRender: () => {
return <>{this.clientList()}</>;
},
},
},
];
const { clients } = this.state;
return (
<>
<br />
<br />
<br />
<div style={{ margin: "10px 15px", overflowX: "auto" }}>
<Link to={"client/create/"} className="btn btn-primary pull-right">
Add Client Data
</Link>
<br />
<br />
<br />
<MUIDataTable data={clients} columns={columns} />
</div>
</>
);
}
}
Thank you for your help and understanding I really appreciate it!
You can just not use map in the clientList() function because you are returning (edit, delete ) of all the clients for each row in the table. you also can pass the row data like I will show in the link on each button and have the _id as a hidden column on your table so that you can have access on it.
import React, { Component } from "react";
import { Link } from "react-router-dom";
import axios from "axios";
import MUIDataTable from "mui-datatables";
const Client = (props) => (
<>
<Link to={"client/edit/" + props.client._id} className="btn btn-primary">
Edit
</Link>
<a
href="client"
onClick={() => {
props.deleteClient(props.client._id);
}}
className="btn btn-danger"
>
Delete
</a>
</>
);
export default class ClientsList extends Component {
constructor(props) {
super(props);
this.deleteClient = this.deleteClient.bind(this);
this.state = {
clients: [{
}]
};
}
componentDidMount() {
axios
.get("http://localhost:5000/clients/")
.then((response) => {
this.setState({ clients: response.data });
})
.catch((error) => {
console.log(error);
});
}
deleteClient(id) {
axios.delete("http://localhost:5000/clients/" + id).then((response) => {
console.log(response.data);
});
this.setState({
clients: this.state.clients.filter((el) => el._id !== id),
});
}
// This is the map I was talking about:
clientList(currentclient) {
// current cleint her is an array that contain all the columns values for the row specify
// assuming that _id will be the first column
return (
<Client
client={currentclient}
deleteClient={this.deleteClient}
key={currentclient[0]}
/>
);
}
render() {
const columns = [
{
name: "_id",
options: {
display: false,
}
},
{
name: "name",
label: "Name",
options: {
filter: true,
sort: true,
},
},
{
name: "address",
label: "Address",
options: {
filter: true,
sort: true,
},
},
{
name: "mobile",
label: "Mobile",
options: {
filter: true,
sort: true,
},
},
{
name: "email",
label: "Email",
options: {
filter: true,
sort: true,
},
},
{
name: "gender",
label: "Gender",
options: {
filter: true,
sort: true,
},
},
{
name: "birthday",
label: "Birthday",
options: {
filter: true,
sort: true,
},
},
{
name: "facebookPage",
label: "Facebook Page",
options: {
filter: true,
sort: true,
},
},
{
name: "facebookName",
label: "Facebook Name",
options: {
filter: true,
sort: true,
},
},
{
name: "existing",
label: "Existing",
options: {
filter: true,
sort: true,
},
},
{
name: "remarks",
label: "Remarks",
options: {
filter: true,
sort: true,
},
},
{
name: "Action",
options: {
customBodyRender: (value, tableMeta, updateValue) => {
return <>{this.clientList(tableMeta.rowData)}</>;
},
},
},
];
const { clients } = this.state;
return (
<>
<br />
<br />
<br />
<div style={{ margin: "10px 15px", overflowX: "auto" }}>
<Link to={"client/create/"} className="btn btn-primary pull-right">
Add Client Data
</Link>
<br />
<br />
<br />
<MUIDataTable data={clients} columns={columns} />
</div>
</>
);
}
}
you can deconstruct clients data from state and then pass it to MUIDataTable
const { clients } = this.state;
const rows = clients.map((client) => {
return {
// assuming atributes
name: client.name,
address: client.address,
mobile: client.mobile,
email: client.email,
gender: client.gender,
birthday: client.birthday,
action: <Link to=`client/edit/${client.id}` calssName='btn btn-primary'> Edit </Link> <a href='client' onClick={() => this.deleteClient(client.id)}> delete </a>
}
}
and then pass it in data props in MUIDataTable
<MUIDataTable data={rows} columns={columns} />
this is an example of a working snippet, tweak it to match you need
const rows = orders.map((order) => {
return {
ref: <Link to={"/orders/" + order.ref}>{order.ref.slice(0, 8)}</Link>,
amount: order.amount,
donated: order.ticketsDetails[0].ticketDonate != "" ? "Yes" : "No",
date: order.createdAt.slice(0, 16),
};
});
const columns = [
{
label: "Ref",
name: "ref",
options: {
filter: true,
sort: true,
},
},
{
label: "Amount",
name: "amount",
options: {
filter: true,
sort: true,
},
},
{
label: "Date",
name: "date",
options: {
filter: true,
sort: true,
},
},
{
label: "Donated",
name: "donated",
options: {
filter: true,
sort: true,
},
},
];
return (
<div className="orders-container">
<MUIDataTable columns={columns} data={rows} />
</div>
);
Is it possible to remove sorting from some columns devextreme-reactive react grid?
I am using the following grids: https://devexpress.github.io/devextreme-reactive/react/grid/docs/guides/sorting/
You can try code over here: https://codesandbox.io/s/1ir3k
import React, { useState } from 'react';
import Paper from '#material-ui/core/Paper';
import {
SortingState,
IntegratedSorting,
} from '#devexpress/dx-react-grid';
import {
Grid,
Table,
TableHeaderRow,
} from '#devexpress/dx-react-grid-material-ui';
import { generateRows } from '../../../demo-data/generator';
export default () => {
const [columns] = useState([
{ name: 'name', title: 'Name' },
{ name: 'gender', title: 'Gender' },
{ name: 'city', title: 'City' },
{ name: 'car', title: 'Car' },
]);
const [rows] = useState(generateRows({ length: 8 }));
const [defaultSorting] = useState([
{ columnName: 'gender', direction: 'desc' },
]);
const [sortingStateColumnExtensions] = useState([
{ columnName: 'gender', sortingEnabled: false },
]);
return (
<Paper>
<Grid
rows={rows}
columns={columns}
>
<SortingState
defaultSorting={defaultSorting}
columnExtensions={sortingStateColumnExtensions}
/>
<IntegratedSorting />
<Table />
<TableHeaderRow showSortingControls />
</Grid>
</Paper>
);
};
I'm new on ReactJS and this is my first page that I created, but I'm having some problems with set variables.
What I need is fill the variable table.data with the values that comes from const response = await api.get('/users') and render the table with this values when page loads.
I have the following code:
import React, { useState, useEffect } from 'react';
import { Fade } from "#material-ui/core";
import MaterialTable from 'material-table';
import { makeStyles } from '#material-ui/core/styles';
import api from '../../services/api.js';
const useStyles = makeStyles(theme => ({
root: {
flexGrow: 1,
width: '70%',
margin: 'auto',
marginTop: 20,
boxShadow: '0px 0px 8px 0px rgba(0,0,0,0.4)'
}
}));
function User(props) {
const classes = useStyles();
const [checked, setChecked] = useState(false);
let table = {
data: [
{ name: "Patrick Mahomes", sector: "Quaterback", email: "patrick#nfl.com", tel: "1234" },
{ name: "Tom Brady", sector: "Quaterback", email: "tom#nfl.com", tel: "5678" },
{ name: "Julio Jones", sector: "Wide Receiver", email: "julio#nfl.com", tel: "9876" }
]
}
let config = {
columns: [
{ title: 'Name', field: 'name' },
{ title: 'Sector', field: 'sector' },
{ title: 'E-mail', field: 'email'},
{ title: 'Tel', field: 'tel'}
],
actions: [
{ icon: 'create', tooltip: 'Edit', onClick: (rowData) => alert('Edit')},
{ icon: 'lock', tooltip: 'Block', onClick: (rowData) => alert('Block')},
{ icon: 'delete', tooltip: 'Delete', onClick: (rowData) => alert('Delete')},
{ icon: 'visibility', tooltip: 'Access', onClick: (rowData) => alert('Access')},
{ icon: "add_box", tooltip: "Add", position: "toolbar", onClick: () => { alert('Add') } }
],
options: {
headerStyle: { color: 'rgba(0, 0, 0, 0.54)' },
actionsColumnIndex: -1,
exportButton: true,
paging: true,
pageSize: 10,
pageSizeOptions: [],
paginationType: 'normal'
},
localization: {
body: {
emptyDataSourceMessage: 'No data'
},
toolbar: {
searchTooltip: 'Search',
searchPlaceholder: 'Search',
exportTitle: 'Export'
},
pagination: {
labelRowsSelect: 'Lines',
labelDisplayedRows: '{from} to {to} for {count} itens',
firstTooltip: 'First',
previousTooltip: 'Previous',
nextTooltip: 'Next',
lastTooltip: 'Last'
},
header: {
actions: 'Actions'
}
}
}
useEffect(() => {
setChecked(prev => !prev);
async function loadUsers() {
const response = await api.get('/users');
table.data = response.data;
}
loadUsers();
}, [])
return (
<>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
<Fade in={checked} style={{ transitionDelay: checked ? '300ms' : '0ms' }}>
<div className={classes.root}>
<MaterialTable editable={config.editable} options={config.options} localization={config.localization} title="Usuários" columns={config.columns} data={table.data} actions={config.actions}></MaterialTable>
</div>
</Fade>
</>
);
}
export default User;
The previous example will show 3 users that I fixed on variable table.data with 4 columns (name, sector, email, tel).
In a functional component, each render is really a new function call. So any variables you declare inside the component and destroyed and re-created. This means that table is set back to your initial value each render. Even if your useEffect is setting it correctly after the first render, it will just be reset on the next.
This is what state is for: to keep track of variables between renders. Replace your let table, with a new state hook.
const [table, setTable] = useState({
data: [
{ name: "Patrick Mahomes", sector: "Quaterback", email: "patrick#nfl.com", tel: "1234" },
{ name: "Tom Brady", sector: "Quaterback", email: "tom#nfl.com", tel: "5678" },
{ name: "Julio Jones", sector: "Wide Receiver", email: "julio#nfl.com", tel: "9876" }
]
});
Then use it like this:
useEffect(() => {
setChecked(prev => !prev);
async function loadUsers() {
const response = await api.get('/users');
setTable(prev => ({...prev, data: response.data});
}
loadUsers();
}, [])
Since table.data is not a state variable, it is regenerated as it was declared originally every time the component renders, meaning that by the time it arrives as a prop to your component it will always be the same value (when you change the value of table.data in useEffect it is too late). You need to change table.data to a state variable, and then in your useEffect hook you can update the value of table.data to the value of response.data. This will cause the component to be re-rendered but with the updated value.
Here's an example of how you might do that:
import React, { useState, useEffect } from 'react';
import { Fade } from "#material-ui/core";
import MaterialTable from 'material-table';
import { makeStyles } from '#material-ui/core/styles';
import api from '../../services/api.js';
const useStyles = makeStyles(theme => ({
root: {
flexGrow: 1,
width: '70%',
margin: 'auto',
marginTop: 20,
boxShadow: '0px 0px 8px 0px rgba(0,0,0,0.4)'
}
}));
function User(props) {
const classes = useStyles();
const [checked, setChecked] = useState(false);
const [tableData, setTableData] = useState([]);
let config = {
columns: [
{ title: 'Name', field: 'name' },
{ title: 'Sector', field: 'sector' },
{ title: 'E-mail', field: 'email'},
{ title: 'Tel', field: 'tel'}
],
actions: [
{ icon: 'create', tooltip: 'Edit', onClick: (rowData) => alert('Edit')},
{ icon: 'lock', tooltip: 'Block', onClick: (rowData) => alert('Block')},
{ icon: 'delete', tooltip: 'Delete', onClick: (rowData) => alert('Delete')},
{ icon: 'visibility', tooltip: 'Access', onClick: (rowData) => alert('Access')},
{ icon: "add_box", tooltip: "Add", position: "toolbar", onClick: () => { alert('Add') } }
],
options: {
headerStyle: { color: 'rgba(0, 0, 0, 0.54)' },
actionsColumnIndex: -1,
exportButton: true,
paging: true,
pageSize: 10,
pageSizeOptions: [],
paginationType: 'normal'
},
localization: {
body: {
emptyDataSourceMessage: 'No data'
},
toolbar: {
searchTooltip: 'Search',
searchPlaceholder: 'Search',
exportTitle: 'Export'
},
pagination: {
labelRowsSelect: 'Lines',
labelDisplayedRows: '{from} to {to} for {count} itens',
firstTooltip: 'First',
previousTooltip: 'Previous',
nextTooltip: 'Next',
lastTooltip: 'Last'
},
header: {
actions: 'Actions'
}
}
}
useEffect(() => {
setChecked(prev => !prev);
async function loadUsers() {
const response = await api.get('/users');
setTableData(response.data);
}
loadUsers();
}, [])
return (
<>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
<Fade in={checked} style={{ transitionDelay: checked ? '300ms' : '0ms' }}>
<div className={classes.root}>
<MaterialTable editable={config.editable} options={config.options} localization={config.localization} title="Usuários" columns={config.columns} data={tableData} actions={config.actions}></MaterialTable>
</div>
</Fade>
</>
);
}
export default User;
I'm new when using materialUI table in react.js, I want to try using react-material table but I got lost as how can I show my data in the table, Let say I have 28 data and in fact it already show the right number in the pagination but the data itself doesn't show anything. this is the documentation link for react-material table Check this.
I already read several topic about this but all of them using tableRow, tableHead, and etc.
this is my component code:
import React, { Component } from 'react';
import MaterialTable from 'material-table';
import { history } from '../../../../Helpers/history';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom';
import { orderActions } from '../../../../Actions/orderActions';
import { withStyles } from '#material-ui/core/styles';
// Component
import './tabledata.css';
const styles = theme => ({
'#global': {
body: {
backgroundColor: theme.palette.common.white,
},
},
});
class Tabledata extends Component {
constructor(props) {
super(props);
// const { orders } = this.props;
this.state = {
columns: [
{ title: 'Nama Pemesanan', field: 'name' },
{ title: 'Status', field: 'status' },
{ title: 'Estimasi Pengerjaan (Hari)', field: 'estimate', type: 'numeric' },
{ title: 'Jumlah Pesanan (pcs)', field: 'unit', type: 'numeric' },
{ title: 'Harga (Rp)', field: 'price', type: 'numeric' },
],
data: [
{
id: 2,
name: 'lala',
status: 'Penyablonan',
estimate: 8,
unit: 36,
price: '36.000.000',
},
],
}
}
componentDidMount() {
if(localStorage.getItem('auth')) {
const { dispatch } = this.props;
dispatch(orderActions.getAllOrder());
// history.push('/dashboard');
}
}
componentWillReceiveProps(newProps){
this.setState({ loading: newProps.loading }); // remove the loading progress when logged in or fail to log in
}
handleChange = prop => event => {
this.setState({ [prop]: event.target.value });
};
change(data){
console.log("Check ID : ", data);
}
render(){
const { orders } = this.props;
console.log("test console : ", orders)
return (
<div className="tabledata-order">
<div className="row item-section">
<div className="col">
<div className="card">
<div className="card-body">
<MaterialTable
title="Daftar Pesanan"
columns={this.state.columns}
key={orders._id}
data={orders}
/>
</div>
</div>
</div>
</div>
</div>
);
}
}
Tabledata.propTypes = {
classes: PropTypes.object.isRequired
};
const mapStateToProps = (state) => {
const { orders } = state.orderPage;
return {
orders
};
}
const connectedTableDataPage = withRouter(connect(mapStateToProps, '', '', {
pure: false
}) (withStyles(styles)(Tabledata)));
export { connectedTableDataPage as Tabledata };
As you can see, this material table have a component like this
<MaterialTable
title="Daftar Pesanan"
columns={this.state.columns}
key={orders._id}
data={orders}
/>
As you can see, in the bottom of the image you can see 1-5 of 28 and in my console there is exactly 28 data but the table itself doesn't show any data
can someone help me? how can I show the data in orders and this is the example of the image json that I have:
Finally I can fix this problem, this answer for you who have facing the same problem with react-material table if your data doesn't show but it show in console.log. you must check the field in column
this.state = {
columns: [
{ title: 'Nama Pemesanan', field: 'name' },
{ title: 'Status', field: 'status' },
{ title: 'Estimasi Pengerjaan (Hari)', field: 'estimate', type: 'numeric' },
{ title: 'Jumlah Pesanan (pcs)', field: 'unit', type: 'numeric' },
{ title: 'Harga (Rp)', field: 'price', type: 'numeric' },
],
data: [
{
id: 2,
name: 'lala',
status: 'Penyablonan',
estimate: 8,
unit: 36,
price: '36.000.000',
},
],
}
let say, json that I got have city, color, and weight then you must state the column field as such:
this.state = {
columns: [
{ title: 'detail Address', field: 'city' },
{ title: 'color', field: 'color' },
{ title: 'weight', field: 'weight' },
],
}
and for the MaterialTable you can just put all the variable you have like this
<MaterialTable
title="Daftar Pesanan"
columns={this.state.columns}
key={orders._id}
data={orders}
/>
and you can get the data like I show you below
I hope this answer can help you who have a hard time with react-material table
I'm trying to develop unit test for my react component with jest and enzyme. So basically my component have resize listener, when resize occured my component will update component state. But i just couldn't get the clientWidth for my react component. Below is some code of my component.
import React, { Component } from "react";
import moment from "moment";
// import PropTypes from "prop-types";
import Table from "./Table";
import Grid from "./Grid";
import ActionBlock from "../ActionBlock";
import ConfirmDialog from './ConfirmDialog';
import ReactTooltip from 'react-tooltip'
import { debounce } from '../../utils';
import styles from './styles.scss';
export default class Pagination extends Component {
constructor(props) {
super(props);
this.state = {
index: props.index,
type: props.type,
config: props.config,
data: props.data,
currentPage: 1,
dataPerPage: 20,
enableActionBlock: props.enableActionBlock,
confirmDialogIndex: null,
confirmDialogActionName: null,
confirmDialogData: null,
width: 0
};
this.handleWindowResize = debounce(this.handleWindowResize.bind(this), 100); //delay trigger resize event
}
componentDidMount() {
this.setState({ width: this.refs.pagination_wrapper.clientWidth })
window.addEventListener('resize', this.handleWindowResize)
}
componentWillUnmount() {
window.removeEventListener('resize', this.handleWindowResize);
}
handleWindowResize = () => {
this.setState({ width: this.refs.pagination_wrapper.clientWidth })
}
render() {
return (
<div ref="pagination_wrapper" className={styles.pagination_wrapper}>
<ReactTooltip />
{this.renderViewType()}
{this.renderConfirmDialog()}
</div>
)
}
}
Pagination.defaultProps = {
enableActionBlock: true,
dataPerPage: 20
};
And below is my test code.
import React from 'react'
import { shallow, mount, render } from 'enzyme';
import Pagination from '../index';
let img = 'https://www.jqueryscript.net/images/Simplest-Responsive-jQuery-Image-Lightbox-Plugin-simple-lightbox.jpg';
let imageStream = 'http://192.168.100.125:8080/';
let imgQuoteError = `http://192.168.100.71/target-data/fr/target-person-images/1111112222233333#Rizkifika-Asanuli'nam/qTD8vYa.jpeg`;
describe('Testing Pagination', () => {
let action = (actionName, indexData) => {
console.log('action APP', actionName, indexData);
}
let dataListProps = {
index: 'id',
type: 'grid',
config: [
{ text: 'Image', type: 'image', textPath: 'image', textColor: 'red', valuePath: 'image' },
{ text: 'Fullname', type: 'string', textPath: 'fullname', valuePath: 'fullname' },
{ text: 'Role', type: 'string', textPath: 'role', valuePath: 'role' },
{ text: 'Datetime', type: 'date', textPath: 'datetime', valuePath: 'datetime' },
{ text: 'Json', type: 'json', textPath: 'json', valuePath: 'json' },
],
data: [
{ id: 305, created_at: '2018-02-23T09:43:08.928Z', rule_detail: { id: 1 }, cam_detail: { id: 2, name: 'kamera huawei' }, vas_detail: { id: 3, name: 'VAS 3' }, image: img },
{ id: 306, created_at: '2018-02-23T09:43:08.928Z', rule_detail: { id: 2, name: '' }, cam_detail: { id: 3, name: 'kamera avigilon' }, vas_detail: { id: 4, name: 'VAS 4' }, image: imageStream },
{ id: 306, created_at: '2018-02-23T09:43:08.928Z', rule_detail: { id: 2, name: null }, cam_detail: { id: 3, name: 'kamera avigilon' }, vas_detail: { id: 4, name: 'VAS 4' }, image: imgQuoteError },
{ id: 306, created_at: '2018-02-23T09:43:08.928Z', rule_detail: { id: 2, name: 'Crowd Behaviour' }, cam_detail: { id: 3, name: 'kamera avigilon' }, vas_detail: { id: 4, name: 'VAS 4' }, image: imageStream },
],
onAction: action,
enableActionBlock: false
}
it('snapshot', () => {
const wrapper = shallow(<Pagination {...dataListProps}/>)
expect(wrapper).toMatchSnapshot();
})
})
I need help for solving this
As pointed by Xarvalus. If wanna access refs, the component have to be mounted first by using mount from import { shallow, mount, render } from 'enzyme';.
But it will have bug (RangeError: Invalid string length). So to overcome this, we have to convert enzyme to json by using import toJson from 'enzyme-to-json';
full working code
import React from 'react';
import { shallow, mount, render } from 'enzyme';
import toJson from 'enzyme-to-json';
import Pagination from '../index';
describe('Testing Pagination', () => {
it('snapshot', () => {
const wrapper = mount(<Pagination {...dataListProps}/>)
expect(toJson(wrapper)).toMatchSnapshot();
})
})
reference issue
You can access the window object inside your component, and so retrieve the window.innerWidth field which is, I guess, what you're looking for.