How to make table row selectable in ReactJS (No React Table) - reactjs

What I tried to achieve is to make the table row clickable with input element so it will select the entire row. Only the problem is how can I add something like a border when the user checks the input field.
<tbody>
{allHotels.map((p: any, index: number) => (
<TableRow key={p.id} selectRow={true}>
{console.log(p.checked)}
<td><input type="checkbox" defaultChecked={p.checked} onClick={() => toggleCheckbox(index)} /></td>
<td>{p.name}</td>
<td>{p.address}</td>
<td>{p.rating}</td>
</TableRow>
))}
</tbody>

The toggleCheckbox function should update the state value representing the checked/unchecked row state.
Given that the selectRow prop on the TableRow component represents the row state, the code could look something like below:
function Hotels() {
const [allHotels, setAllHotels] = useState([]);
useEffect(() => {
fetch('/api/hotels')
.then((response) => response.json())
.then((hotels) => hotels.map((hotel) => ({ ...hotel, selected: false })))
.then(setAllHotels);
}, []);
const toggleCheckbox = (index) => {
const newHotels = [...allHotels];
newHotels[index].selected = !newHotels[index].selected;
setAllHotels(newHotels);
};
return (
<tbody>
{allHotels.map((hotel, index) => (
<TableRow key={hotel.id} selectRow={hotel.selected}>
<td>
<input
type="checkbox"
defaultChecked={hotel.checked}
onClick={() => toggleCheckbox(index)}
/>
</td>
<td>{hotel.name}</td>
<td>{hotel.address}</td>
<td>{hotel.rating}</td>
</TableRow>
))}
</tbody>
);
}
Please note that the code needs to be updated according to your needs. But I hope you get the idea.

Related

How to save changes of a input in table row as single and multiple?

I have some inputs in every row(PreviewItemRow.js) of Table component. I get the data from Redux store. I keep PreviewItemRow changes as internal state. There is also a save button in every button whose onClick event makes an api call to server.
Problem is I want user to save(make api call) his changes as batch requests and also use should be able to save as individual row.
If I reflect changes directly to redux store changes state in redux whenever user presses a button in keyboard, I wont be able to be sure if changes reflected to server.
If I keep the name as component internal state, I can not track changes from SaveAll button.
So how can I Implement to save changes from a button individual row and a button in parent component ?
Parent Table Component
const ParentTableComp = (props) => {
const cases = useSelector(store => store.taskAppReducer.Case.cases);
const handleSaveAllClick = () => {
dispatch(previewBulkSave({
taskId: selectedTask.taskId,
caseData: cases.map(item => ({
name: item.caseName,
}))
}))
.then(() => {
saveSuccess("All saved.");
})
.catch((err) => {
saveError(err);
});
};
return (
<div>
<Button
type='button'
color='primary'
onClick={handleSaveAllClick}
>
Save All
</Button>
<Table>
<thead>
<tr>
<th>Name</th>
</tr>
</thead>
<tbody>
{cases.map((item, index) => (
<tr key={item.caseId}>
<PreviewCaseItem
case={item}
/>
</tr>
))}
</tbody>
</Table>
</div>
);
};
This is the Row component.
const PreviewItemRow = (props) => {
const [name, setName] = useState(props.case.name)
const dispatch = useDispatch();
const handleSaveButtonClick = () => {
dispatch(saveCase({
taskType: taskType,
updatedCase: {
...props.case,
name
},
}))
.then(() => {
saveSuccess("Case Updated");
})
.catch((err) => {
saveError(err);
});
};
const handleNameChange = (event) => {
setName(event.target.value)
}
return (
<div>
<td style={{ width: 100 }}>
<Input
type={"text"}
id={`name-${props.case.caseId}`}
value={name}
onChange={handleNameChange}
/>
</td>
</div>
);
};

How can I memoize redux state?

I'm struggling with useSelector in component and I would like to memoize the value of useSelector to just read the value from the cache if other states changes and don't get the state from redux for every component rerender.
Here's the selector which I've used in parent component:
const campaignListSelector = createSelector(
(state) => state.campaignReducer,
campaignReducer => campaignReducer.campaignList ? campaignReducer.campaignList.campaigns : []
);
const campaigns = useSelector(campaignListSelector);
Also I have a component which is called Button:
const Button = React.memo(({ onClick, counter, title }) => {
return <button onClick={onClick}>{counter}</button>
})
And this is the code which I've developed in parent component:
const ParentComponent = (props) => {
const dispatch = useDispatch()
const [state, setState] = React.useState({
pageNo: 1,
counter: 0,
})
useEffect(() => {
console.log("state.pageNo", state.pageNo)
dispatch(CampaignActionCreators.fetchCampaignList(state.pageNo, -1, null, null, 'All', '', 'All'));
}, [state.pageNo])}
const campaignListSelector = createSelector(
(state) => state.campaignReducer,
campaignReducer => campaignReducer.campaignList ? campaignReducer.campaignList.campaigns : []
);
const campaigns = useSelector(campaignListSelector);
const table = React.useMemo(() => {
console.log(campaignList)
return <table className="table table-list-layout">
<thead>
<tr>
<th width="15%">
{Utils.i18n('campaign', true)}
</th>
<th className="text-center">
{Utils.i18n('campaignSetting', true)}
</th>
</tr>
</thead>
<tbody>
{campaignList && campaignList.map((campaign, index) => (
<tr key={index}>
<td>
<LinkButton to={`/Campaign/View?id=${campaign.id}`}
text={campaign.title}
/>
<td className="text-center">
-
</td>
</tr>
))}
</tbody>
</table>
})
const onClick = () =>
setState({
...state,
counter: state.counter + 1
})
return <React.Fragment>
<Button onClick={onClick} counter={state.counter} />
{table}
</React.Fragment>
My problem is that every time I click on the Button compponent, table value which I've used it with React.memo will be re-rendered and console.log(campaignList) will show a line on the console. How can I prevent table from re-rendering while I just click on button and inline state of the ParentComponent will be changed? How can I memoize the value of campaigns?

How to filter a table with select options in React (component function)

Closed. This question needs details or clarity. It is not currently accepting answers.
Add details and clarify the problem you’re solving. This will help others answer the question. You can edit the question or post a new one.
Closed 4 mins ago.
I can't find how to filter a table in React with the Select (dropdown). I found an example on Google but they used Node but their code doesn't work on my React app. I prefer using components not class functions. This is my code. And I want to filter the table exemple by select options ???:
const [data, getData] = useState([])
const URL = 'https://jsonplaceholder.typicode.com/posts'
useEffect(() => {
fetchData()
}, [])
const fetchData = () => {
fetch(URL)
.then((res) =>
res.json())
.then((response) => {
console.log(response);
getData(response);
})
}
return (
<>
<Select options=["A","B","C"] />
<table>
<tr>
<th>User Id</th>
<th>Id</th>
<th>Title</th>
<th>Description</th>
</tr>
{data.map((item, i) => (
<tr key={i}>
<td>{item.userId}</td>
<td>{item.id}</td>
<td>{item.title}</td>
<td>{item.body}</td>
</tr>
</table>
</>
Since you didn't specify which property to filter by, I filtered by id.
You didn't specify which select component you are using. Hence, I've replaced your select with Native HTML Select
In short, in order to filter by selected option you have to:
Use the onchange event on the select (all the non-native select components like Material-ui, etc. also have it). You have to have a handler that assigns a UseState variable to the value selected.
In render method, you have to use Array.filter to filter by the preserved value.
Here is the working code:
import "./styles.css";
import React, { useEffect, useState } from "react";
export default function App() {
const [data, getData] = useState([]);
const [selectedId, setSelectedId] = useState([]);
const URL = "https://jsonplaceholder.typicode.com/posts";
useEffect(() => {
fetchData();
}, []);
const fetchData = () => {
fetch(URL)
.then((res) => res.json())
.then((response) => {
console.log(response);
getData(response);
});
};
const handleSelectChange = (e) => {
console.log(e.target.value);
setSelectedId(e.target.value);
};
return (
<>
<select onChange={handleSelectChange} name="ids" id="ids">
<option value="1">Id 1</option>
<option value="2">Id 2</option>
<option value="3">Id 3</option>
<option value="4">Id 4</option>
</select>
<table>
<tr>
<th>User Id</th>
<th>Id</th>
<th>Title</th>
<th>Description</th>
</tr>
{data
.filter((value) => {
return Number(value.id) === Number(selectedId);
})
.map((item, i) => (
<tr key={i}>
<td>{item.userId}</td>
<td>{item.id}</td>
<td>{item.title}</td>
<td>{item.body}</td>
</tr>
))}
</table>
</>
);
}
Working Sandbox: https://codesandbox.io/s/wonderful-feistel-qgrm0f?file=/src/App.js

Dropdown Item not making api call on first try

Im having an issue where when I select a new Dropdown Item the Item isnt saved or the api call isnt made until I select the item again. So if I select Sales from the the Dropdown menu and and refresh the old value will still be there. Ive consoled out the value of the state (permission and adminID) and as soon as I select a dropdown item they are both set to the state so Im not sure whats causing it not to set on the first try.
const UserCard = (props) => {
const [dropdownOpen, setDropdownOpen] = useState(false);
const [permission, setPermission] = useState()
const [adminID, setAdminID] = useState()
const item = props.item;
const toggle = () => {
setDropdownOpen(prevState => !prevState)
};
const adminUpdate = (perm, id) => {
setPermission(perm)
if(!permission || !adminID){
return
}else{
api.UserManagement.put(
adminID,
{permissions: [permission]}
).catch(err => {
console.log("error", err, err.status)
})
}
}
console.log(permission, adminID)
return (
<>
<tr key={item.id}>
<td>{item.name || "-"}</td>
<td>{item.email}</td>
<td>{!permission ? item.permissions : permission}</td>
<td>
<Dropdown style={{ fontSize: "30px",borderColor: "white", backgroundColor: "white", color: "gray"}} isOpen={dropdownOpen} toggle={toggle}>
<DropdownToggle
tag="span"
data-toggle="dropdown"
aria-expanded={dropdownOpen}
>
...
</DropdownToggle>
<DropdownMenu onClick={() => setAdminID(item.id)} key={item.id}>
<DropdownItem value={"Admin"} onClick={e => adminUpdate(e.target.value)}>Admin</DropdownItem>
<DropdownItem value={"Sales"} onClick={e => adminUpdate(e.target.value)}>Sales</DropdownItem>
<DropdownItem value={"Medical"} onClick={e => adminUpdate(e.target.value)}>Medical</DropdownItem>
</DropdownMenu>
</Dropdown>
</td>
</tr>
</>
);
};
const UserList = ({}) => {
const [list, setList] = useState([]);
const [errors, setErrors] = useState();
useEffect(() => {
api.UserManagement.list()
.then((result) => {
let list = [];
if (result) {
list = result.items;
}
setList(list);
})
.catch((err) => {
if (err.status) {
console.log("error", err, err.status);
setErrors([err]);
}
});
}, []);
return (
<>
<ContentRow>
<Row>
<Col md="8">
<h1 style={{ marginTop: "25px" }}>
<Link to={"/"}>
<Button color="link"><</Button>
</Link>
<span style={{ fontSize: "25px" }}>User Management</span>
</h1>
</Col>
<div
className="d-flex justify-content-around align-items-stretch"
style={{ marginTop: "15px" }}
>
<Link to={"/invitations"}>
<Button className="header-button" block color="primary">
Invite Users
</Button>
</Link>
</div>
<Col md="4"></Col>
</Row>
</ContentRow>
<ContentRow>
<ErrorList errors={errors}/>
<Table>
<thead>
<tr>
<th width="30%">User</th>
<th width="30%">Email Address</th>
<th width="30%">Role</th>
<th width="10%"></th>
</tr>
</thead>
<tbody>
{!list ? (
<PageLoadSpinner />
) : (
list.map((item) => <UserCard item={item} />)
)}
</tbody>
</Table>
</ContentRow>
</>
);
};
export default UserList;
Reason
The code is not working due to improper usage of useState hook. useState is asynchronous in nature. Due to this, the variables are not updated with proper values when you are using it. The current code is assuming variables to be updated in synchronous manner.
setPermission(perm) is done in one line and permission variable is used in next line. But permission variable won't be updated at that time.
Same seems for admin variable.
That's why for the first time, the code doesn't work (the variables are not updated). But when you click again, the older values are picked; condition is satisfied; making it work.
Read more about it here.
Fix
There can be two ways in which this code can be fixed.
Approach 1
In the if condition, you can use the function arguments directly instead of variable from use state.
The relevant fixed code will be:
const UserCard = (props) => {
...
const adminUpdate = (perm, id) => {
if(!perm || !id){ // Change this condition here
return
}else{
api.UserManagement.put(
id,
{permissions: [perm]} // Change here as well
).catch(err => {
console.log("error", err, err.status)
})
}
}
return (
<>
...
</DropdownToggle>
<DropdownMenu onClick={() => setAdminID(item.id)} key={item.id}>
// Pass id onClick of DropdownItem also
<DropdownItem value={"Admin"} onClick={e => adminUpdate(e.target.value, item.id)}>Admin</DropdownItem>
<DropdownItem value={"Sales"} onClick={e => adminUpdate(e.target.value, item.id)}>Sales</DropdownItem>
<DropdownItem value={"Medical"} onClick={e => adminUpdate(e.target.value, item.id)}>Medical</DropdownItem>
</DropdownMenu>
</Dropdown>
...
</>
);
};
This approach almost removes the usage of useState hook as you are directly using variables passed from function
Approach 2
You can also use useEffect hook along with useState. useEffect takes dependency array as second argument and will trigger function call when any of the variable is changed. So, the logic will be splitted into 2 parts:
Variables will be updated separately by useState
Api call will be triggered when any of variable is updated in useEffect.
The relevant fixed code will be:
const UserCard = (props) => {
const [dropdownOpen, setDropdownOpen] = useState(false);
const [permission, setPermission] = useState()
const [adminID, setAdminID] = useState()
const item = props.item;
const toggle = () => {
setDropdownOpen(prevState => !prevState)
};
useEffect(() => {
if (permission && adminID) {
api.UserManagement.put(
adminID,
{permissions: [permission]}
).catch(err => {
console.log("error", err, err.status)
})
}
}, [permission, adminID]) // This will trigger the function call when any of these 2 variables are modified.
return (
<>
...
</DropdownToggle>
// setAdminID on click
<DropdownMenu onClick={() => setAdminID(item.id)} key={item.id}>
// setPermission onClick
<DropdownItem value={"Admin"} onClick={e => setPermission(e.target.value)}>Admin</DropdownItem>
<DropdownItem value={"Sales"} onClick={e => setPermission(e.target.value)}>Sales</DropdownItem>
<DropdownItem value={"Medical"} onClick={e => setPermission(e.target.value)}>Medical</DropdownItem>
</DropdownMenu>
</Dropdown>
</td>
</tr>
</>
);
};
Approach 1 vs Approach 2
It's upto you which approach to chose from. Both will get you the result but differ in base approach. Approach 1 is triggering the API call directly on click of dropdown item but Approach 2 is updating the variables on click of dropdown items and the change of those variables is triggering the API call.
Hope it helps. Revert for any doubts/clarifications.

React-Hooks: How to sort data in ascending & descending order and show in reusable data table component

In my application, I want to create a reusable data table component. Data is coming from the Backend, for that data I need to do the sort. The data are numbers & strings. Now I want to show data in Ascending order and Descending order button. Here is my data table code:
//Table Component
const Table = ({ headers, data }) => {
return (
<table>
<thead>
<tr>
{headers.map(head => (
<th>{head}</th>
))}
</tr>
</thead>
<tbody>
{data.map(row => (
<tr>
{headers.map(head => (
<td>{row[head]}</td>
))}
</tr>
))}
</tbody>
</table>
//app.js
export default function App() {
const headers = ['userId', 'id', 'title', 'body'];
useEffect(() => {
const getPosts = async () => {
const { data } = await axios.get('https://jsonplaceholder.typicode.com/posts');
setData(data);
};
getPosts();
}, []);
const filteredData = slice.filter(
post => post.title.toLowerCase().includes(keyword) || post.body.toLowerCase().includes(keyword),
);
const onInputChange = e => {
e.preventDefault();
setKeyword(e.target.value.toLowerCase());
};
return (
<div>
div className="table-search-form">
<input
className="form-control"
type="text"
placeholder="Search..."
onChange={onInputChange}
/>
</div>
<DataTable headers={headers} posts={filteredData} />
</div>
);
}
I found this solution using a data-table package. Here https://www.npmjs.com/package/react-data-table-component-extensions

Resources