I create table that render data which receive from server. But in this table I have cols which not all user should to see. This is my code:
class TableBody extends React.Component {
state = {
columns: {
id: props => (
<td key="id">
{props._id}
</td>
),
title: props => (
<td key="title">
{props.title}
</td>
),
manager: props => (
<td key="manager">
{props.manager}
</td>
)
},
hiddenColumns: {
user: ['manager']
}
}
In state I init my columns and add columns restrictions for user (he can not see manager column). In render I do next:
render() {
const hiddenColumns = this.state.hiddenColumns[this.props.role] || [];
const columns = Object.keys(this.state.columns).filter(key => {
return hiddenColumns.indexOf(key) === -1
});
return this.props.companies.map(company => (
<tr key={offer._id}>
{columns.map(element => this.state.columns[element](company))}
</tr>
));
}
I get hidden columns for current user and filter key in columns. After this I use map to go over data which receive from server and inside map I go over for each filtered columns and send element (props).
In the future, more columns will be added to this table and make this:
{columns.map(element => this.state.columns[element](company))}
will not be effective.
Maybe I can create main template and after init remove columns which user should not to see, but I don't know how.
Please help me
Thank you
I think you are doing this completely wrong. you should never filter data specific to user role on client side.
Ideally such data should be filtered on server side and then send to client only role specific data.
With your current approach user can simply inspect browser network tab and read all other restricted columns.
Related
I'm trying to update a react state that holds nested values. I want to update data that is 3 levels deep.
Here is the state that holds the data:
const [companies, setCompanies] = useState(companies)
Here is the data for the first company (the companies array holds many companies):
const companies = [
{
companyId: 100,
transactions: [
{
id: "10421A",
amount: "850",
}
{
id: "1893B",
amount: "357",
}
}
]
Here is the code for the table component:
function DataTable({ editCell, vendors, accounts }) {
const columns = useMemo(() => table.columns, [table]);
const data = useMemo(() => table.rows, [table]);
const tableInstance = useTable({ columns, data, initialState: { pageIndex: 0 } }, useGlobalFilter, useSortBy, usePagination);
const {
getTableProps,
getTableBodyProps,
headerGroups,
prepareRow,
rows,
page,
state: { pageIndex, pageSize, globalFilter },
} = tableInstance;
return (
<Table {...getTableProps()}>
<MDBox component="thead">
{headerGroups.map((headerGroup) => (
<TableRow {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map((column) => (
<DataTableHeadCell
{...column.getHeaderProps(isSorted && column.getSortByToggleProps())}
width={column.width ? column.width : "auto"}
align={column.align ? column.align : "left"}
sorted={setSortedValue(column)}
>
{column.render("Header")}
</DataTableHeadCell>
))}
</TableRow>
))}
</MDBox>
<TableBody {...getTableBodyProps()}>
{page.map((row, key) => {
prepareRow(row);
return (
<TableRow {...row.getRowProps()}>
{row.cells.map((cell) => {
cell.itemsSelected = itemsSelected;
cell.editCell = editCell;
cell.vendors = vendors;
cell.accounts = accounts;
return (
<DataTableBodyCell
noBorder={noEndBorder && rows.length - 1 === key}
align={cell.column.align ? cell.column.align : "left"}
{...cell.getCellProps()}
>
{cell.render("Cell")}
</DataTableBodyCell>
);
})}
</TableRow>
);
})}
</TableBody>
</Table>
)
}
For example, I want to update the amount in the first object inside the transactions array. What I'm doing now is update the entire companies array, but doing this rerenders the whole table and creates problems. Is there a way I can only update the specific value in a manner that rerenders just the updated field in the table without rerendering the whole table? I've seen other answers but they assume that all values are named object properties.
FYI, I'm not using any state management and would prefer not to use one for now.
You have to copy data (at least shallow copy) to update state:
const nextCompanies = { ...companies };
nextCompanies.transactions[3].amount = 357;
setState(nextCompanies);
Otherwise react won't see changes to the original object. Sure thing you can use memoization to the child component to skip useless rerenders. But I strongly recommend to provide an optimisation only when it is needed to optimise. You will make the code overcomplicated without real profit.
When updating state based on the previous state, you probably want to pass a callback to setCompanies(). For example:
setCompanies((currCompanies) => {
const nextCompanies = [...currCompanies];
// modify nextCompanies
return nextCompanies;
})
Then, in order for React to only re-render the elements that changed in the DOM, you should make sure to set the key prop in each of those elements. This way, React will know which element changed.
// inside your component code
return (
<div>
companies.map(company => (
<Company key={company.id} data={company} />
))
</div>
)
Does this solve the problem? If not, it may be helpful to add some more details so we can understand it fully.
What I'm doing now is update the entire companies array, but doing
this rerenders the whole table and creates problems.
When you say it creates problems what type of problems exactly? How does re-rendering create problems? This is expected behavior. When state or props change, by default a component will re-render.
You seem to be asking two questions. The first, how to update state when only modifying a subset of state (an amount of a transaction). The second, how to prevent unnecessary re-rendering when render relies on state or props that hasn't changed. I've listed some strategies for each below.
1. What is a good strategy to update state when we only need to modify a small subset of it?
Using your example, you need to modify some data specific to a company in a list of companies. We can use map to iterate over each company and and conditionally update the data for the company that needs updating. Since map returns a new array, we can map over state directly without worrying about mutating state.
We need to know a couple things first.
What transaction are we updating?
What is the new amount?
We will assume we also want the company ID to identify the correct company that performed the transaction.
We could pass these as args to our function that will ultimately update the state.
the ID of the company
the ID of the transaction
the new amount
Any companies that don't match the company ID, we just return the previous value.
When we find a match for the company ID, we want to modify one of the transactions, but return a copy of all the other previous values. The spread operator is a convenient way to do this. The ...company below will merge a copy of the previous company object along with our updated transaction.
Transactions is another array, so we can use the same strategy with map() as we did before.
const handleChangeAmount = ({ companyId, transactionId, newAmount }) => {
setCompanies(() => {
return companies.map((company) => {
return company.id === companyId
? {
...company,
transactions: company.transactions.map((currTransaction) => {
return currTransaction.id === transactionId
? {
id: currTransaction.id,
amount: newAmount
}
: currTransaction;
})
}
: company;
});
});
};
2. How can we tell React to skip re-rendering if state or props hasn't changed?
If we are tasked with skipping rendering for parts of the table that use state that didn't change, we need a way of making that comparison within our component(s) for each individual company. A reasonable approach would be to have a reusable child component <Company /> that renders for each company, getting passed props specific to that company only.
Despite our child company only being concerned with its props (rather than all of state), React will still render the component whenever state is updated since React uses referential equality (whether something refers to the same object in memory) whenever it receives new props or state, rather than the values they hold.
If we want to create a stable reference, which helps React's rendering engine understand if the value of the object itself hasn't changed, the React hooks for this are useCallback() and useMemo()
With these hooks we can essentially say:
if we get new values from props, we re-render the component
if the values of props didn't change, skip re-rendering and just use the values from before.
You haven't listed a specific problem in your question, so it's unclear if these hooks are what you need, but below is a short summary and example solution.
From the docs on useCallback()
This is useful when passing callbacks to optimized child components that rely on reference equality to prevent unnecessary renders
From the docs on useMemo()
This optimization helps to avoid expensive calculations on every render.
Demo/Solution
https://codesandbox.io/s/use-memo-skip-child-update-amount-vvonum
import { useState, useMemo } from "react";
const companiesData = [
{
id: 1,
transactions: [
{
id: "10421A",
amount: "850"
},
{
id: "1893B",
amount: "357"
}
]
},
{
id: 2,
transactions: [
{
id: "3532C",
amount: "562"
},
{
id: "2959D",
amount: "347"
}
]
}
];
const Company = ({ company, onChangeAmount }) => {
const memoizedCompany = useMemo(() => {
console.log(
`AFTER MEMOIZED CHECK COMPANY ${company.id} CHILD COMPONENT RENDERED`
);
return (
<div>
<p>Company ID: {company.id}</p>
{company.transactions.map((t, i) => {
return (
<div key={i}>
<span>id: {t.id}</span>
<span>amount: {t.amount}</span>
</div>
);
})}
<button onClick={onChangeAmount}> Change Amount </button>
</div>
);
}, [company]);
return <div>{memoizedCompany}</div>;
};
export default function App() {
const [companies, setCompanies] = useState(companiesData);
console.log("<App /> rendered");
const handleChangeAmount = ({ companyId, transactionId, newAmount }) => {
setCompanies(() => {
return companies.map((company) => {
return company.id === companyId
? {
...company,
transactions: company.transactions.map((currTransaction) => {
return currTransaction.id === transactionId
? {
id: currTransaction.id,
amount: newAmount
}
: currTransaction;
})
}
: company;
});
});
};
return (
<div className="App">
{companies.map((company) => {
return (
<Company
key={company.id}
company={company}
onChangeAmount={() =>
handleChangeAmount({
companyId: company.id,
transactionId: company.transactions[0].id,
newAmount: Math.floor(Math.random() * 1000)
})
}
/>
);
})}
</div>
);
}
Explanation
On mount, the child component renders twice, once for each company.
The button will update the amount on the first transaction just for that company.
When the button is clicked, only one <Company /> component will render while the other one will skip rendering and use the memoized value.
You can inspect the console to see this in action. Extending this scenario, if you had 100 companies, updating the amount for one company would result in 99 skipped re-renders with only one new component rendering for the updated company.
how to fetch data in api in react js based id.i make one table in my below code i want to make when i click on row first then fetch data its based on 1st click row.https://mocki.io/v1/4d51be0b-4add-4108-8c30-df4d60e8df54. this is my api i try to make but iam unable to do that.i want when i click on row1 then fetch data which id is 2 in api how can we do that
class TableComponent extends React.Component {
state = {};
handleRowClick = async () => {
// make an API call here, sth like
const url = "https://mocki.io/v1/4d51be0b-4add-4108-8c30-df4d60e8df54";
const response = await fetch(url);
this.setState({
...response,
});
};
render() {
var dataColumns = this.props.data.columns;
var dataRows = this.props.data.rows;
var tableHeaders = (
<thead>
<tr>
{" "}
{dataColumns.map(function (column) {
return <th> {column} </th>;
})}{" "}
</tr>{" "}
</thead>
);
var tableBody = dataRows.map((row) => {
return (
<tr onClick={this.handleRowClick}>
{" "}
{dataColumns.map(function (column) {
return (
<td>
{" "}
<a target="_blank" rel="noopener noreferrer" href={row.url}>
{" "}
{row[column]}{" "}
</a>
</td>
);
})}{" "}
</tr>
);
});
// Decorate with Bootstrap CSS
return (
<table className="table table-bordered table-hover" width="100%">
{" "}
{tableHeaders} {tableBody}{" "}
</table>
);
}
}
// Example Data
var tableData = {
columns: ['Service_Name', 'Cost/Unit'],
rows: [{
'Service_Name': 'airplane',
'Cost/Unit': 50
}, {
'Service_Name': 'cat',
'Cost/Unit': 50
},{
'Service_Name': 'fruits',
'Cost/Unit': 50
}, {
'Service_Name': 'pool',
'Cost/Unit': 50
}]
};
ReactDOM.render(
<TableComponent data = {tableData} />,
document.getElementById('table-component'));
You need to add unique ID in your tableData for every row data. now that ID will be used while creating table as key for every row as React's guidelines and also passed to handleRowClick function based on the row user is click
https://codepen.io/rohit-ambre/pen/NWdJpbp
check above codepen, I have made small changes in handleClick function. (currently it returns all the data)
But to complete your expectations here, your mock API is dumb as in it has no logic to return based on any parameter passed to it. I'm not sure about mocki.io if it is possible or no. Otherwise, you can create a simple express app in local to return based on ID passed. then your URL will changed as following
https://mocki.io/v1/4d51be0b-4add-4108-8c30-df4d60e8df54/:id and API should only return data associated with that id.
EDIT: I have modified my codepen to use other API which has logic.
Also if you're getting CORS error then for time being install this chrome extension.
I have created small glitch project which has 2 APIs to return data based on params-wise API or Query wise, I have commented both URLs in my codepen.
API info:
https://grandiose-mulberry-garnet.glitch.me/query?id=<id> this API uses query parameters to accept id, if you don't pass id like https://grandiose-mulberry-garnet.glitch.me/query then it'll return all records
https://grandiose-mulberry-garnet.glitch.me/params/<id> this API is uses route params to accept id, it is mandatory to pass id here.
FYI: In both APIs you need to replace <id> with any value like 1,2,4,3, etc
I have an array of Json objects('filteredUsers' in below code) and I'm mapping them to a html table. Json object would look like {id: xyz, name: Dubov}
The below code would display a html table with a single column or basically a list. Each row will have name of user and a grey checkbox(unchecked) next to it initially. I want to select users in the table and when I select or click on any item in table, the checkmark has to turn green(checked).
<table className="table table-sm">
{this.state.filteredUsers && (
<tbody>
{this.state.filteredUsers.map((user) => (
<tr key={user.id}>
<td onClick={() => this.selectUser(user)}>
<span>{user.name}</span> //Name
<div className={user.selected? "checked-icon": "unchecked-icon"}> //Checkmark icon
<span class="checkmark"> </span>
</div>
</td>
</tr>
))}
</tbody>
)}
</table>
I tried setting a 'selected' key to each object. Initially object doesn't have 'selected' key so it will be false(all unchecked). I set onClick method for 'td' row which sets 'selected' key to object and sets it to true. Below function is called onClick of td or table item.
selectUser = (user) => {
user.selected = !user.selected;
};
Now the issue is this will only work if I re-render the page after every onClick of 'td' or table item. And I'm forced to do an empty setState or this.forcedUpdate() in selectUser method to trigger a re-render. I read in multiple answers that a forced re-render is bad.
Any suggestions or help would be highly appreciated. Even a complete change of logic is also fine. My end goal is if I select an item, the grey checkmark has to turn green(checked) and if I click on it again it should turn grey(unchecked). Similarly for all items. Leave the CSS part to me, but help me with the logic. Thanks.
How about something like this:
const Users = () => {
const [users, setUsers] = useState([])
useEffect(() => {
// Fetch users from the API when the component mounts
api.getUsers().then((users) => {
// Add a `selected` field to each user and store them in state
setUsers(users.map((user) => ({ ...user, selected: true })))
})
}, [])
const toggleUserSelected = (id) => {
setUsers((oldUsers) =>
oldUsers.map((user) =>
user.id === id ? { ...user, selected: !user.selected } : user
)
)
}
return (
<ul>
{users.map((user) => (
<li key={user.id} onClick={() => toggleUserSelected(user.id)}>
<span>{user.name}</span>
<div className={user.selected ? "checked-icon" : "unchecked-icon"}>
<span class="checkmark" />
</div>
</li>
))}
</ul>
)
}
I've used hooks for this but the principles are the same.
This looks to be a state issue. When updating data in your react component, you'll need to make sure it's happening in one of two ways:
The data is updated by a component higher up in the tree and then is passed to this component via props.
this will cause your component to re-render with the new props and data, updating the "checked" property in your HTML.
In your case, it looks like you're using this second way:
The data is stored in component state. Then, when you need to update the data, you'd do something like the below.
const targetUser = this.state.filteredUsers.find(user => user.id === targetId)
const updatedUser = { ...targetUser, selected: !targetUser.selected }
this.setState({ filteredUsers: [ ...this.state.filteredUsers, updatedUser ] })
Updating your state in this way will trigger an update to your component. Directly modifying the state object without using setState does not trigger the update.
Please keep in mind that, when updating objects in component state, you'll need to pass a new, full object to setState in order to trigger the update. Something like this will not work: this.setState({ filteredUsers[1].selected: false });
Relevant documentation
im trying to populate all the dropdowns in my table using a single api request.
Im trying to develop a forms portal. one of the pages have a table with dropdowns on a few columns. this table has add row and delete row features. now i'm trying to populate the dropdowns in the table(4 specifically) using an api call. the api call is succesfull in obtaining the data to be populated onto the state variables. but since im using a single class for the calling and rendering function i'm having trouble rendering the data to the corresponding columns. how do i render the data in this.state.backupof into the select tags of the first column. please help me...
export default class EditableTable extends React.Component {
componentDidMount() {
axios
.get(`http://xx.xx.x.xxx:8000/backup_request/dropdown`)
.then(response=>response.data)
.then(data=>{this.setState({backupof: data.backup_of_list});
this.setState({servertypelist:data.server_type_list});
this.setState({frequencylist:data.frequency_list});
this.setState({backupdrivelist:data.backup_drive})
console.log(this.state.backupof,"axios worked");});
}
constructor(props) {
super(props);
this.state = {
data:[],
backupof:[],
servertypelist:[],
frequencylist:[],
backupdrivelist:[],
};
this.columns = [
{
title: 'Backup Of',
dataIndex: 'backupOf',
align:'center',
render: (select, record) =>
this.state.dataSource.length >= 1 ? (
<Select defaultValue='Select' name='backupOf' >
{this.state.backupof.map(person => (
<Option value={person.name} label={person.name} key={person.name}>
{person.name}
</Option>
))}
</Select>) : null
},
}
Fixed it...
I called the api in the parent form and then passed the value to the child table.
<Select defaultValue='Select' name='requestType'>
{this.props.requesttypedrop.map(person => (
<Option value={person.name} label={person.name} key={person.name}>
{person.name}
</Option>
))}
</Select>
this is the api call in parent
componentDidMount() {
axios
.get(`http://xx.xx.x.xxx:8000/asset_request/request_type_dropdown/`)
.then(response=>response.data)
.then(data=>{this.setState({request_type:data.request_type_list});
this.setState({required_type:data.server_type_list});
this.setState({asset_type:data.frequency_list});
this.setState({loading:false});
})
.catch(error => {console.log(error);});
}
it was an isssue of timing. doing this solved it.
I am facing an issue to order the rows of a fetched API data dynamically, i.e if the rows data within the API is changed (+/- row) to render it automatically. I am trying to print the data fetched from an API into a table. Anyhow the columns are well printed dynamically, but the function that I am using for columns this.state.columns.map(( column, index ) => doesn't function in the same way for the rows. I think i am misleading the ES6 standard, but I am not sure. Here is how it's look like, if the rows are not hardcoded.
Here is my code sample:
class App extends React.Component
{
constructor()
{
super();
this.state = {
rows: [],
columns: []
}
}
componentDidMount()
{
fetch( "http://ickata.net/sag/api/staff/bonuses/" )
.then( function ( response )
{
return response.json();
} )
.then( data =>
{
this.setState( { rows: data.rows, columns: data.columns } );
} );
}
render()
{
return (
<div id="container" className="container">
<h1>Final Table with React JS</h1>
<table className="table">
<thead>
<tr> {
this.state.columns.map(( column, index ) =>
{
return ( <th>{column}</th> )
}
)
}
</tr>
</thead>
<tbody> {
this.state.rows.map(( row ) => (
<tr>
<td>{row[0]}</td>
<td>{row[1]}</td>
<td>{row[2]}</td>
<td>{row[3]}</td>
<td>{row[4]}</td>
<td>{row[5]}</td>
<td>{row[6]}</td>
<td>{row[7]}</td>
<td>{row[8]}</td>
<td>{row[9]}</td>
<td>{row[10]}</td>
<td>{row[11]}</td>
<td>{row[12]}</td>
<td>{row[13]}</td>
<td>{row[14]}</td>
<td>{row[15]}</td>
</tr>
) )
}
</tbody>
</table>
</div>
)
}
}
ReactDOM.render( <div id="container"><App /></div>, document.querySelector( 'body' ) );
Instead, I was able to print the rows harcoded, if I give value to the 'td' elements, but I want to print it dynamically, in case the data within the API has been changed.
You are welcome to contribute directly to my Repo: Fetching API data into a table
Here is how looks like my example, when the rows values has been hardcoded within 'td' elements.
Any suggestions will be appreciated!
Just as you are iterating over columns and rows you could add an additional loop for iterating over row cells.
For example:
this.state.rows.map(row => (
<tr>{row.map(cell => (
<td>{cell}</td>
))}
</tr>
))
Note that you probably want to add a key prop:
Keys help React identify which items have changed, are added, or are
removed. Keys should be given to the elements inside the array to give
the elements a stable identity