Checkbox does not work with keys in React - reactjs

I am writing a component where there are checkboxes. Using map I need to use keys in React. I created function that generates unique keys. But after that my checkboxes stop working properly. I cannot click some of them, some styles dissapear. Why is that going on? I thought keys are just such stuff needed for giving a unique id for React.
// generating id
export function uid(): string {
return (performance.now().toString(36) + Math.random().toString(16)).replace(/\./g, '');
}
// my component
export default function Table(props:ITable) {
if (props.loader) {
return <Loader />;
}
return (
<table className="table">
<thead>
<tr>
<th>
{props.actions.map((element) =>
element.onChangeAll &&
<Checkbox
key={uid()}
indeterminate
onChange={element.onChangeAll}
label={element.label}
disabled={props.columns.every((el) => el.disabled)}
/>)}
</th>
{props.headers.map((header) =>
<TableHeader
key={uid()}
header={header}
/>)}
{props.actions.map((element) =>
element.header &&
<th key={uid()}>{element.header}</th>)}
</tr>
</thead>
<tbody>
{props.columns.map((data) =>
<tr
className={classNames(
data.checked ? 'row-active' : 'row',
data.disabled && 'row-disabled'
)}
key={uid()}
>
<td>
{props.actions.map((element) =>
element.onChangeCheckbox &&
<Checkbox
onChange={element.onChangeCheckbox}
disabled={data.disabled}
checked={data.checked}
id={data.id.toString()}
key={uid()}
/>)}
</td>
{props.headers.map((header) =>
<TableRow
key={uid()}
columns={data}
header={header}
/>)}
{props.actions.map((element) =>
element.element &&
<td
key={uid()}
onClick={() => {
element.deleteRaw && element.deleteRaw(data.id);
element.openModal && element.openModal(data);
}}
>
{element.element}
</td>)}
</tr>
)}
</tbody>
</table>
);
}

Related

Toggle icons in react after iterating through Object and update the form

I have a form with a social object, I want to add an icon to the social notification that has a boolean value and also want to toggle the icon and update the form.
export default function UserProfile(props) {
const form = useForm({
initialValues: {
name: props.userProfile.name,
socials: {
email: {
notifications: true,
},
facebook: {
notifications: false,
},
twitter: {
notifications: false,
},
},
},
});
return (
<>
<form
method="post"
className="pt-5"
key={props.userProfile.id}
onSubmit={form.onSubmit(handleSubmit)}
>
<TextInput
label="Name"
placeholder={props.userProfile.name}
{...form.getInputProps("name")}
/>
<Tabledata
socials={{ ...form.getInputProps("socials") }}
session={session}
/>
<button type="submit">Save</button>
</form>
</>
)
}
The table data component is :
function Tabledata({ socials }) {
// console.log(socials.value["email"]["notifications"]);
function ToggleButton() {
const [isChecked, setIsChecked] = useState(true);
return isChecked? <CheckCircleIcon
className="h-6 w-6 text-green-500"
onClick={() => setIsChecked(!isChecked)}
/>
: <XCircleIcon
className="h-6 w-6 text-red-500"
onClick={() => setIsChecked(!isChecked)}
/>
}
return (
<>
<Table>
<thead>
<tr>
<th>Social channel</th>
<th>Notifications</th>
</tr>
</thead>
<tbody>
{Object.keys(socials.value).map((data, index) => (
<tr key={index}>
<td>{data}</td>
<td>
{socials.value[data]["notifications"]? (
<ToggleButton />
) : (
"Null"
)}
</td>
</tr>
))}
</tbody>
</Table>
</>
);
}
But I'm unable to toggle the value of the notification. How can I approach that? I was trying the above method and also I tried the below method, but not getting the desired result. I'm not sure how to do that.
function Tabledata({ socials }) {
return (
<>
<Table>
<thead>
<tr>
<th>Social channel</th>
<th>Notifications</th>
</tr>
</thead>
<tbody>
{Object.keys(socials.value).map((data, index) => (
<tr key={index}>
<td>{data}</td>
<td>
{socials.value[data]["notifications"]? (
<CheckCircleIcon
className="h-6 w-6 text-green-500"
onClick={() =>
console.log(`change the notification to false`)
}
/>
) : (
<XCircleIcon
className="h-6 w-6 text-red-500"
onClick={() =>
console.log(`change the notification to true`)
}
/>
)}
</td>
</tr>
))}
</tbody>
</Table>
</>
);
}
Please leave you suggestions. I tried different StackOverflow suggestions but could not able to solve my problem.
I refactored your code, I think you are on the right track, your mistake is that you are creating a function toggleButton and you call the function like a component <toggleButton />, I just updated your code . I hope it works.
function Tabledata({ socials }) {
// console.log(socials.value["email"]["notifications"]);
const [isChecked, setIsChecked] = useState(true);
function toggleButton() {
return isChecked ? (
<CheckCircleIcon
className="h-6 w-6 text-green-500"
onClick={() => setIsChecked(false)}
/>
) : (
<XCircleIcon
className="h-6 w-6 text-red-500"
onClick={() => setIsChecked(true)}
/>
);
}
return (
<>
<Table>
<thead>
<tr>
<th>Social channel</th>
<th>Notifications</th>
</tr>
</thead>
<tbody>
{Object.keys(socials.value).map((data, index) => (
<tr key={index}>
<td>{data}</td>
<td>
{communications.value[data]["notifications"] ? (
<>{toggleButton()}</>
) : (
"Null"
)}
</td>
</tr>
))}
</tbody>
</Table>
</>
);
}
And if you want to make component, then you have to make the toggleButton func outside the TableData, as you can see the below code.
function ToggleButton() {
const [isChecked, setIsChecked] = useState(true);
return isChecked ? (
<CheckCircleIcon
className="h-6 w-6 text-green-500"
onClick={() => setIsChecked(!isChecked)}
/>
) : (
<XCircleIcon
className="h-6 w-6 text-red-500"
onClick={() => setIsChecked(!isChecked)}
/>
);
}
function Tabledata({ socials }) {
// console.log(socials.value["email"]["notifications"]);
return (
<>
<Table>
<thead>
<tr>
<th>Social channel</th>
<th>Notifications</th>
</tr>
</thead>
<tbody>
{Object.keys(socials.value).map((data, index) => (
<tr key={index}>
<td>{data}</td>
<td>
{communications.value[data]["notifications"] ? (
<ToggleButton />
) : (
"Null"
)}
</td>
</tr>
))}
</tbody>
</Table>
</>
);
}

all user list gmail ,id ,name are not showing in userlist

function UserListScreen() {
const dispatch = useDispatch();
const userList = useSelector((state) => state.userList);
const { loading, error, users } = userList;
useEffect(() => {
dispatch(listUsers);
}, [dispatch]);
return (
<div>
<h1>Users</h1>
{loading ? (
<Loader />
) : error ? (
<Message variant="danger">{error}</Message>
) : (
<Table striped bordered hover responsive className="table-sm">
<thead>
<tr>
<th>ID</th>
<th>NAME</th>
<th>EMAIL</th>
<th>ADMIN</th>
<th></th>
</tr>
</thead>
<tbody>
{users.map((user) => (
<tr key={user._id}>
<td>{user._id}</td>
<td>{user.name}</td>
<td>{user.email}</td>
<td>
{user.isAdmin ? (
<i className="fas fa-check" style={{ color: "green" }}></i>
) : (
<i className="fas fa-check" style={{ color: "red" }}></i>
)}
</td>
</tr>
))}
</tbody>
</Table>
)}
</div>
);
}
export default UserListScreen;
all user list will show their id , name, and email but only show <h2> not showing data from the backend

Word is not getting added until the page is refreshed in TableItem component in React

TableItem component added without any data in UI. Could somebody help on this. On refereshing the UI, added data is shown with details in TableItem component.
Table Component Code
import TableItem from "./TableItem";
function Table({ searchWord }) {
const dispatch = useDispatch();
const dictData = useSelector((state) => state.dictionary);
useEffect(() => {
dispatch(getDictionaryAsync());
}, [dispatch]);
return (
<table className="table table-striped">
<thead>
<tr>
<th scope="col">Word</th>
<th scope="col">Description</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
{dictData &&
dictData
.filter((e) =>
searchWord === ""
? e
: e.word &&
e.word.toLowerCase().includes(searchWord.toLowerCase())
)
.map((item) => (
<TableItem item={item} key={item.id} searchWord={searchWord} />
))}
</tbody>
</table>
);
}
export default Table;
Below is the TableItem Component Code which i am trying to update,
When i add a word to dictionary it will fetch the details from the server and display it in the React app.
function TableItem({ item }) {
const [modal, setModal] = useState(false);
const openModal = () => {
setModal(true);
};
return (
<>
<tr key={item.id}>
<td style={{ textTransform: "capitalize" }}>{item.word}</td>
<td>
<b style={{ textTransform: "capitalize" }}>
{item.items && item.items[0].category} -{" "}
</b>
{item.items && truncate(item.items[0].definitions[0])}
</td>
<td>
<button className="btn btn-danger btn-sm " onClick={openModal}>
View
</button>
</td>
</tr>
<Modal isOpen={modal} ariaHideApp={true}>
<div className="modal-header">
<h3 className="modal-word-header">
{item.word && item.word.toUpperCase()}
</h3>
<button
className="btn btn-danger btn-sm"
onClick={() => setModal(false)}
>
<i class="fa fa-times" aria-hidden="true"></i>
</button>
</div>
<div className="model-content">
<p>
{item.items &&
item.items.map((e) => {
return (
<>
<i>{e.category}</i>
<ul>
{e.definitions.map((def) => {
return <li>{def}</li>;
})}
</ul>
</>
);
})}
</p>
</div>
</Modal>
</>
);
}
Better add your TableItem component code!
Below code works fine and updated the UI on change in the Data in TableItem,
useEffect(() => {
dispatch(getDictionaryAsync());
}, [dispatch, dictData]); *<--updated code*

Select rows issue in pagination react table

I've made a table with one column as checkbox to select that row. So if the user checks this row's checkbox, I'll add isChecked : true property in state and on uncheck will change isChecked: false of that in state. Each page is having 10rows. The issue is when I checked the 1st row checkbox of 1st page and when I go to Next Page somehow the 1st row of next page checkbox also appears checked. However only 1st row is set to true in state. Whats the issue? What wrong I'm doing can anyone tell? Thanks in advance!
import React,{Component} from 'react';
import { Table,Button,Input } from 'reactstrap';
import SelectedUsers from './SelectedUsers';
import { yellow } from '#material-ui/core/colors';
import Icon from '#material-ui/core/Icon';
class Users extends Component {
constructor(props) {
super(props);
this.state = {
users : [],
pageSize: 10,
pageIndex: 0,
selectedUsers : [],
filterCandidate : '',
searchVal : ""
};
}
componentDidMount() {
const userLink = 'api';
fetch(userLink, {
method: 'GET'
})
.then(res => res.json())
.then(data => {
this.setState({
users : data
})
console.log(data)
})
}
onSelectUser = (e,i) => {
const copy_users = this.state.users.slice() ;
const checked = e.target.checked
copy_users[i].isChecked = checked
this.setState({ copy_users})
// console.log( e.target.value)
}
handlePrevPageClick = (event) => {
this.setState(prevState => ({
pageIndex: prevState.pageIndex > 0 ? prevState.pageIndex - 1 : 0
}));
}
handleNextPageClick = (event) => {
this.setState(prevState => ({
pageIndex:
prevState.pageIndex <
Math.floor(prevState.users.length / prevState.pageSize)
? prevState.pageIndex + 1
: prevState.pageIndex
}));
}
render() {
let profile = 'Profile Image';
return (
<div className="bets_page">
<Table striped responsive>
<thead>
<tr>
<th>Select</th>
<th>Player Name</th>
<th>Level<Icon style={{ color: yellow[800] }} fontSize="small">star</Icon></th>
<th>Avatar</th>
<th>BET</th>
<th>Wins<Icon style={{ color: yellow[800] }} fontSize="small">euro</Icon></th>
<th>Lost</th>
<th>Price</th>
</tr>
</thead>
<tbody>
{this.state.users.slice(
this.state.pageIndex * this.state.pageSize,
this.state.pageIndex * this.state.pageSize + this.state.pageSize
).map((data,i) => (
<tr key={i}>
<td>
<label className="checkbox">
<input type="checkbox"
checked={data.isChecked}
key={i}
value={data.Name}
onChange={(e) => this.onSelectUser(e,i)}/>
</label>
</td>
<td>{data.Name}</td>
<td></td>
<td><img src={data[profile]} alt={data.Name}
className="avatar"/></td>
<td>{data.Bet}</td>
<td></td>
<td></td>
<td>{data.Price}</td>
</tr>
))}
</tbody>
</Table>
<div>
<Button onClick={event => this.handlePrevPageClick(event)} className="m-2">
{"<"}
</Button>Page {this.state.pageIndex+1}
<Button onClick={event => this.handleNextPageClick(event)} className="m-2">
{">"}
</Button>
</div>
</div>
}
}
export default Users;
when you slice the users and apply map on them the 'i' variable starts from 0 for each page. you should add 'this.state.pageIndex * this.state.pageSize' to 'i' variable whenever you set it for key and you send that to onSelectUser
render() {
return (
<div className="bets_page">
<Table striped responsive>
<thead>
<tr>
<th>Select</th>
<th>Player Name</th>
</tr>
</thead>
<tbody>
{this.state.users
.slice(
this.state.pageIndex * this.state.pageSize,
this.state.pageIndex * this.state.pageSize + this.state.pageSize
)
.map((data, i) => {
const index = i + this.state.pageIndex * this.state.pageSize;
return (
<tr key={index}>
<td>
<label className="checkbox">
<input
type="checkbox"
checked={data.isChecked}
key={i}
value={data.Name}
onChange={(e) => this.onSelectUser(e, index)}
/>
</label>
</td>
<td>{data.Name}</td>
</tr>
);
})}
</tbody>
</Table>
<div>
<Button
onClick={(event) => this.handlePrevPageClick(event)}
className="m-2"
>
{"<"}
</Button>
Page {this.state.pageIndex + 1}
<Button
onClick={(event) => this.handleNextPageClick(event)}
className="m-2"
>
{">"}
</Button>
</div>
</div>
);
}
I simplified your code and I created the online demo here

React: Proper way to update an array of objects in state

I am a professional web developer teaching myself react. I created this table as part of a larger form.
The table is invoked inside the form component
<ProductList
products={this.state.products}
onChange={products => this.sendUpdate('products', products)}
/>
this.sendUpdate:
sendUpdate(field, value) {
this.setState({[field]: value});
socket.emit('updateItem', this.state.id, {[field]: value});
}
That part is all working great with all my form updates. but now I am trying to figure out how to process the updates inside the table. Each product is a row of the table invoked like this:
<tbody>
{this.props.products.map((product, i) =>
<Product key={i} data={product} products={this}/>
)}
</tbody>
What is the proper way to update the state when I type in one of the inputs?
<FormControl
value={this.props.data.species}
onClick={e => this.updateProduct('species', e.target.value)}
/>
full code for ProductList
import React from "react";
import {Button, Table, FormControl} from "react-bootstrap";
class Product extends React.Component {
updateField(...props){
this.props.products.updateProduct(this.data, ...props)
}
render() {
return (
<tr>
<td>
<FormControl
value={this.props.data.species}
onClick={e => this.updateProduct('species', e.target.value)}
/>
</td>
<td><FormControl/></td>
<td><FormControl/></td>
<td><FormControl/></td>
<td><FormControl/></td>
<td><FormControl/></td>
<td><FormControl type="number"/></td>
<td><Button bsStyle="danger" onClick={() => this.props.products.deleteProduct(this.props.data)}>X</Button></td>
</tr>
);
}
}
export default class ProductList extends React.Component {
constructor(...props) {
super(...props);
}
addProduct() {
let products = this.props.products.concat([{timestamp: Date.now()}]);
this.props.onChange(products);
}
updateProduct(product, field, newValue) {
this.props.products;
// ???
}
deleteProduct(product) {
let products = this.props.products.filter(p => {
return p !== product
});
this.props.onChange(products);
}
render() {
return (
<Table responsive>
<thead>
<tr>
<th>Species</th>
<th>Dried</th>
<th>Cut</th>
<th>Dimensions Green</th>
<th>Dimensions Dry</th>
<th>Color</th>
<th>Quantity</th>
<th className="text-right">
<Button bsStyle="success" bsSize="xsmall" onClick={() => this.addProduct()}>Add</Button>
</th>
</tr>
</thead>
<tbody>
{this.props.products.map(product => <Product key={product.timestamp} data={product} products={this}/>)}
</tbody>
</Table>
);
}
}
This is what I ended up with based on the accepted answer:
import React from "react";
import {Button, Table, FormControl} from "react-bootstrap";
export default class ProductList extends React.Component {
constructor(...props) {
super(...props);
}
addProduct() {
let products = this.props.products.concat([{}]);
this.props.onChange(products);
}
updateProduct(product, field, newValue) {
const products = this.props.products.map(p => {
return p === product ? {...p, [field]: newValue} : p;
});
this.props.onChange(products);
}
deleteProduct(product) {
let products = this.props.products.filter(p => {
return p !== product
});
this.props.onChange(products);
}
render() {
return (
<Table responsive striped>
<thead>
<tr>
<th>Species</th>
<th>Dried</th>
<th>Cut</th>
<th>Dimensions Green</th>
<th>Dimensions Dry</th>
<th>Color</th>
<th>Quantity</th>
<th className="text-right">
<Button bsStyle="success" bsSize="xsmall" onClick={() => this.addProduct()}>Add</Button>
</th>
</tr>
</thead>
<tbody>
{this.props.products.map((product, i) => this.renderRow(i, product, this))}
</tbody>
</Table>
);
}
renderRow(i, product) {
return (
<tr key={i}>
<td>
<FormControl
value={product.species || ''}
onChange={e => this.updateProduct(product, 'species', e.target.value)}
/>
</td>
<td>
<FormControl
value={product.dried || ''}
onChange={e => this.updateProduct(product, 'dried', e.target.value)}
/>
</td>
<td>
<FormControl
value={product.cut || ''}
onChange={e => this.updateProduct(product, 'cut', e.target.value)}
/>
</td>
<td>
<FormControl
value={product.dimensionsGreen || ''}
onChange={e => this.updateProduct(product, 'dimensionsGreen', e.target.value)}
/>
</td>
<td>
<FormControl
value={product.dimensionsDry || ''}
onChange={e => this.updateProduct(product, 'dimensionsDry', e.target.value)}
/>
</td>
<td>
<FormControl
value={product.color || ''}
onChange={e => this.updateProduct(product, 'color', e.target.value)}
/>
</td>
<td>
<FormControl
type="number"
value={product.quantity || 0}
onChange={e => this.updateProduct(product, 'quantity', e.target.value)}
/>
</td>
<td><Button bsStyle="danger" onClick={() => this.deleteProduct(product)}>X</Button></td>
</tr>
);
}
}
In your ProductsList's render(), change the array map to something like:
{this.props.products.map((product, index) => <Product key={product.timestamp} data={product} index={index} products={this}/>)}
Then in your Product's change the updateField() to:
updateField(...props){
this.props.products.updateProduct(this.props.index, ...props)
}
And finally, change ProductsList's updateProduct() to:
updateProduct(index, field, newValue) {
const products = this.props.products.map((product, productIndex)) => {
if (index === productIndex) {
return {
...product,
[field]: newValue
};
}
return product;
})
this.props.onChange(products);
}
Also, there's a slight typo in Product render. The FormControl's onClick should read onClick={e => this.updateField('species', e.target.value)}.

Resources