I found a tutorial to implement DataTable in React App but I have no idea how can I custom my tr, td, colspan etc...
My DataTable.js is :
import React, { Component } from 'react';
const $ = require('jquery');
$.DataTable = require('datatables.net');
class App extends Component {
componentDidMount() {
this.$el = $(this.el);
this.$el.DataTable({
data: this.props.data,
columns: this.props.columns
})
}
render() {
return (
<div className="table-responsive">
<table className="table" ref={el => this.el = el}>
</table>
</div>
);
}
}
And after in my Test.js :
import React, { Component } from 'react';
import DataTable from './DataTable';
import axios from 'axios';
class Test extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
columns: [
{ title: "Id" },
{ title: "Category" },
{ title: "Title" },
{ title: "Command 1" },
{ title: "Command 2" },
{ title: "Command 3" }
]
}
}
componentDidMount() {
this._getFilteredItems();
}
_getFilteredItems = () => {
axios.post('http://localhost:8080/items/category', { category: "category1" })
.then((res) => {
var test = res.data.map((e) => Object.values(e)); // to transform objet in array
this.setState({ data: test });
})
.catch(error => { console.log(error) });
}
display = () => {
if(this.state.data.length > 0){
return (
<DataTable
data={this.state.data}
columns={this.state.columns}>
</DataTable>
);
}
}
render() {
return (
<div>
{this.display()}
</div>
);
}
}
export default Test;
My data received from my backend is like this :
[
["5e9c231facad1424801f5167", "category1", "title", "command1", "command2", "command3"],
["5e9c2337acad1424801f58ce", "category1", "title", "command1", "command2", "command3"],
["5eaa105b82d1130017d31dbe", "category1", "title", "command1", "command2", "command3"],
]
The thing is I would like to custom my tr, td, colspan etc... I mean, I would like for example put the title with a colspan="5" and my command1, command2 and command3 in the same td.
Do you have any idea how can I do that ? Thanks
I found, I simply initialized my table as I wanted in the render instead of using props like the tutorial mentionned it :
import React, { Component } from 'react';
import axios from 'axios';
const $ = require('jquery');
$.DataTable = require('datatables.net');
class Test extends Component {
constructor(props) {
super(props);
this.state = {
data: []
}
}
componentDidMount() {
this._getFilteredItems();
}
_getFilteredItems = () => {
axios.post('http://localhost:8080/items/category', { category: "category1" })
.then((res) => {
this.setState({data: res.data});
this.$el = $(this.el);
this.$el.DataTable({});
})
.catch(error => { console.log(error) });
}
render() {
return (
<div className="table-responsive">
<table className="table" ref={el => this.el = el}>
<thead>
<tr>
<th>Title</th>
<th>Commands</th>
</tr>
</thead>
<tbody>
{this.state.data.map((elmt, i) => (
<tr key={i}>
<td>
{elmt.title}
</td>
<td>
{elmt.command1} <br/>
{elmt.command2} <br/>
{elmt.command3} <br/>
</td>
</tr>
))}
</tbody>
</table>
</div>
);
}
}
export default Test;
Related
I am using Django Rest Framework to send data to React app. But the data is being shown on screen.
The code isnt returning any errors thus making it difficult to see whats going wrong. This is my second React project thus i am not too familiar with React & JS as of now.
This is my code:
import { render } from "#testing-library/react";
import axios from "axios";
import React, { Component, useState, useEffect } from "react";
const api_url = "http://localhost:8000/api/CBView/"
class StocksHomePage extends Component {
constructor(props) {
super(props);
this.state = {
isFetching:false,
data_s :[]
};
}
componendDidMount() {
this.fetchData();
this.timer = setInterval(() => this.fetchData(), 50);
}
fetchData = () => {
this.setState({...this.state, isFetching:true});
axios.get(api_url)
.then (response => {
this.setState({data_s:response.data[0]})
})
.catch(e => {
console.log(e);
this.setState({...this.state, isFetching:false});
});
};
render() {
return (
<div>
{this.state.data_s.map(m => <p>{m.co_S}</p>)}
{/* <p data={this.state.data_s.co_S} ></p> */}
<ul>
<li isKey dataField='co_N'></li>
<li dataField='co_S'></li>
<li dataField='price'></li>
</ul>
<p>{this.state.isFetching ? 'Fetching users...' : ''}</p>
</div>
)
}
}
I fixed the issue, all i had to do was include maps function with variable to represent those values. Here is my code:
class StocksHomePage extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
loaded: false,
placeholder: "loading"
};
}
componentDidMount() {
axios
.get("http://localhost:8000/CBView")
.then(response => {return response.data; })
.then(data => {this.setState(() => {
return {data, loaded:true};
});
});
}
handleClick = (props) => {
<HashRouter>
<Route path='/StocksDetail' component={StocksDetail} />
</HashRouter>
};
render() {
return (
<table className="table">
<thead>
<tr>
<th>Name</th>
<th>Price</th>
<th>Price/ Chng</th>
<th>Mkt Cap</th>
<th>Volume</th>
<th>Turnover</th>
</tr>
</thead>
<tbody>
{this.state.data.map(item => {
return (
<tr key={item.co_S}>
<button onCLick={this.handleClick(item.co_S)}><td >{item.co_N}</td></button>
<td>{item.price}</td>
<td>{item.p_chng_pc}</td>
<td>{item.Mkt_cap}</td>
<td>{item.volume}</td>
<td>{item.volume * item.price}</td>
</tr>
);
})};
</tbody>
</table>
);
}
}
export default StocksHomePage;
I'm developing an app using React. However, facing some issue with reflecting the {column.label} at column header field. The code works with any plain text but not for inputs in {}.
Below is the code for reference.
filename: tableHeader.jsx:
import React, { Component } from "react";
class TableHeader extends Component {
raiseSort = (path) => {
const sortColumn = { ...this.props.sortColumn };
if (sortColumn.path === path)
sortColumn.order = sortColumn.order === "asc" ? "desc" : "asc";
else {
sortColumn.path = path;
sortColumn.order = "asc";
}
this.props.onSort(sortColumn);
};
render() {
return (
<thead>
<tr>
{this.props.columns.map((column) => (
<th
key={column.path || column.key}
onClick={() => this.raiseSort(column.path)}
>
{column.label}
</th>
))}
</tr>
</thead>
);
}
}
export default TableHeader;
filename: moviesTable.jsx:
import React, { Component } from "react";
import Like from "./common/like";
import TableHeader from "./common/tableHeader";
class MoviesTable extends Component {
columns = [
{ path: "title", lable: "Title" },
{ path: "genre.name", lable: "Genre" },
{ path: "numberInStuck", lable: "Stock" },
{ path: "dailyRentalRate", lable: "Rate" },
{ key: "Like" },
{ key: "Delete" },
];
render() {
const { movies, onDelete, onLike, onSort, sortColumn } = this.props;
return (
<table className="table">
<TableHeader
columns={this.columns}
sortColumn={sortColumn}
onSort={onSort}
/>
<tbody>
{movies.map((movie) => (
<tr key={movie._id}>
<td> {movie.title} </td>
<td> {movie.genre.name} </td>
<td> {movie.numberInStock} </td>
<td> {movie.dailyRentalRate} </td>
<td>
<Like liked={movie.liked} onClick={() => onLike(movie)} />
</td>
<td>
<button
onClick={() => onDelete(movie)}
className="btn btn-danger btn-sm"
>
Delete
</button>
</td>
</tr>
))}
</tbody>
</table>
);
}
}
export default MoviesTable;
fileName: movie.jsx:
import React, { Component } from "react";
import { getMovies } from "../services/fakeMovieService";
import { getGenres } from "../services/fakeGenreService";
import Pagination from "./common/pagination";
import { paginate } from "../utils/paginate";
import ListGorup from "./common/listGroup";
import MoviesTable from "./moviesTable";
import _ from "lodash";
class Movies extends Component {
state = {
movies: [],
genres: [],
pageSize: 4,
currentPage: 1,
sortColumn: { path: "title", order: "asc" },
};
componentDidMount() {
const genres = [{ _id: "", name: "All Genres" }, ...getGenres()];
this.setState({ movies: getMovies(), genres });
}
handleDelete = (movie) => {
const movies = this.state.movies.filter((m) => m._id !== movie._id);
this.setState({ movies });
};
handleLike = (movie) => {
const movies = [...this.state.movies];
const index = movies.indexOf(movie);
movies[index] = { ...movie };
movies[index].liked = !movies[index].liked;
this.setState({ movies });
};
handlePageChange = (page) => {
this.setState({ currentPage: page });
};
handleGenreSelect = (genre) => {
this.setState({ selectedGenre: genre, currentPage: 1 });
};
handleSort = (sortColumn) => {
this.setState({ sortColumn });
};
render() {
const { length: count } = this.state.movies;
const {
pageSize,
currentPage,
movies: allMovies,
selectedGenre,
sortColumn,
} = this.state;
if (count === 0) return <p>No movies in the DB..</p>;
const filtered =
selectedGenre && selectedGenre._id
? allMovies.filter((m) => m.genre._id === selectedGenre._id)
: allMovies;
const sorted = _.orderBy(filtered, [sortColumn.path], [sortColumn.order]);
const movies = paginate(sorted, currentPage, pageSize);
return (
<div>
<div className="row">
<div className="col-3">
<ListGorup
items={this.state.genres}
onItemSelect={this.handleGenreSelect}
selectedItem={this.state.selectedGenre}
/>
</div>
<div className="col">
<p>Showing {filtered.length} movies in the DB. </p>
<MoviesTable
movies={movies}
onLike={this.handleLike}
onDelete={this.handleDelete}
onSort={this.handleSort}
sortColumn={sortColumn}
/>
<Pagination
itemsCount={filtered.length}
onPageChange={this.handlePageChange}
pageSize={pageSize}
currentPage={currentPage}
/>
</div>
</div>
</div>
);
}
}
export default Movies;
The below code from file1(tableHeader.jsx) is responsible to give the column name. Not sure, why it's not coming.
<thead>
<tr>
{this.props.columns.map((column) => (
<th
key={column.path || column.key}
onClick={() => this.raiseSort(column.path)}
>
{column.label}
</th>
))}
</tr>
</thead>
Looking for to hearing from you.
Your columns list only contains objects with the key lable (misspelled). It should be label:
columns = [
{ path: "title", /* not lable ! */ label: "Title" },
{ path: "genre.name", label: "Genre" },
{ path: "numberInStuck", label: "Stock" },
{ path: "dailyRentalRate", label: "Rate" },
{ key: "Like" },
{ key: "Delete" },
];
I'm currently trying to create a table where each row has a checkbox that can be enabled or disabled.
I have created 3 files:
ChangeUserGroup.jsx - This is where the data is read from a json file
UserGroupForm.jsx - Starts the form and passes the this.prop.permissions on to the next file, which contains the table and the checkboxes.
TableShowPermissions.jsx - Contains the table and the problematic function + checkbox
I would like for the data in the ChangeUserGroup.jsx state(called groupDetails.permissions) to control if the checkbox for the given permission is initialized with "defaultChecked". But i am getting the following error:
The files contain the following:
ChangeUserGroup.jsx
import React, { Component } from 'react';
import { Helmet } from 'react-helmet';
import { Container, Row, Col, Card, CardHeader, CardBody, Table, Button } from 'reactstrap';
import jsonData from '_testdata/userGroups';
import LoadingIcon from '_components/LoadingIcon';
import UserGroupForm from '_components/UserGroupForm';
class ChangeUserGroup extends Component {
constructor(props) {
super(props);
this.state = {
isLoading: false,
userGroupId: 0,
groupDetails: []
}
}
componentDidMount() {
this.setState({ isLoading: true });
const { userGroupId } = this.props.match.params
this.setState({
userGroupId: userGroupId
});
jsonData.userGroups.filter((a) => a.id.toString() === userGroupId).map((b) => this.setState({
groupDetails: b,
isLoading: false
}));
}
render() {
const { groupDetails, isLoading } = this.state;
if (isLoading) {
return <LoadingIcon />;
}
return (
<>
<Helmet>
<title>{global.siteName + " - Brugergrupper"}</title>
</Helmet>
<main>
<section className="section section-shaped section-lg">
<Container>
<Row className="justify-content-center">
<Col lg="12">
<Card className="bg-secondary shadow border-0">
<CardHeader className="bg-white">
<h1 className="text-center">{groupDetails.name}</h1>
</CardHeader>
<CardBody className="px-lg-5 py-lg-5">
<UserGroupForm
id={groupDetails.id}
groupName={groupDetails.name}
position={groupDetails.position}
permissions={groupDetails.permissions} />
</CardBody>
</Card>
</Col>
</Row>
</Container>
</section>
</main>
</>
);
}
}
export { ChangeUserGroup };
UserGroupForm.jsx
import React, { Component } from 'react';
import { Form } from "reactstrap";
import CustomFormInput from '_components/FormStuff/CustomFormInput';
import TableShowPermissions from '_components/UserGroups/TableShowPermissions';
class UserGroupForm extends Component {
render() {
const { id, groupName, position, permissions } = this.props;
return (
<Form>
<CustomFormInput pLabel="Gruppe navn" pIcon="fa-user" pType="text" pShowLabel="on" pValue={groupName} />
<CustomFormInput pLabel="Gruppe position" pIcon="fa-user" pType="text" pShowLabel="on" pValue={position} />
<hr />
<TableShowPermissions
thePermissions={permissions} />
</Form>
);
}
}
export default UserGroupForm;
TableShowPermissions.jsx
import React, { Component } from 'react';
import jsonData from 'UserPermissions';
import { Table, Button } from "reactstrap";
class TableShowPermissions extends Component {
constructor(props) {
super(props);
this.checkIfPermissionAdded = this.checkIfPermissionAdded.bind(this);
}
checkIfPermissionAdded(checkPerm) {
const { thePermissions } = this.props;
thePermissions.split('|').map(permC => {
//console.log(checkPerm + " -- " + permC)
if (checkPerm === permC) {
return true;
}
});
return false;
}
render() {
return (
<Table className="align-items-center table-bordered" responsive>
<thead className="thead-light">
<tr>
<th scope="col">Permission navn</th>
<th scope="col">Beskrivelse</th>
<th scope="col" />
</tr>
</thead>
<tbody>
{jsonData.permissions.map(perm => (
<tr key={perm.id}>
<th scope="row">
{perm.permission}
</th>
<td>
{perm.description}
</td>
<td className="text-right">
<label className="custom-toggle">
<input type="checkbox" defaultChecked={this.checkIfPermissionAdded(perm.permission)} />
<span className="custom-toggle-slider rounded-circle" />
</label>
</td>
</tr>
))}
</tbody>
</Table>
);
}
}
export default TableShowPermissions;
JSON DATA
{
"userGroups": [
{
"id": 1,
"name": "Administrator",
"position": "Admin",
"permissions": "ADMIN_ROOT|"
},
{
"id": 2,
"name": "Moderator",
"position": "Mod",
"permissions": "ADMIN_SETTINGS_GENERAL|ADMIN_CASES_SEEALL|ADMIN_SETTINGS_CATEGORIES"
},
{
"id": 3,
"name": "Supporters",
"position": "Supporter",
"permissions": "ADMIN_CASES_SEEALL|"
}
]
}
Any help would be appreciated! :)
If after a few react renders you do have the value, it most likely means it just isn't there yet in the first renders (this can happen if you are getting the data from a request).
You can either add an if condition to only run the split when thePermissions exist or add a default value to it.
checkIfPermissionAdded(checkPerm) {
const { thePermissions } = this.props;
if (thePermissions) {
thePermissions.split('|').map(permC => {
//console.log(checkPerm + " -- " + permC)
if (checkPerm === permC) {
return true;
}
});
}
return false;
}
or
checkIfPermissionAdded(checkPerm) {
const { thePermissions = '' } = this.props;
thePermissions.split('|').map(permC => {
//console.log(checkPerm + " -- " + permC)
if (checkPerm === permC) {
return true;
}
});
return false;
}
It is possible that none is what you desire in your code but it might help you out knowing why it is happening.
Initialize your Loading initial state as true. Set your state in the constructor like this:
constructor(props) {
super(props);
this.state = {
isLoading: true,
userGroupId: props.match.params,
groupDetails: []
}
}
Remove the first two setState from your componentDidMount. Each time a setState is called the render fucntion is called.
I am new to react/flux and I try to practice, so I try to develop a pizza shop frontend with fake backend (created with FAKER).
My problem is, that I want to update my JSON data, but I can't figure out how and where can I do this. Important, I just want to update "preparedAt" value in my JSON data, I want to keep the other values ("sequentialNumber", "id", "name").
How can I keep the old values, and just update only one value?
For example, I want to update this:
{"sequentialNumber": 1,"id": 61301,"name": "molestiae","preparedAt": "2020-05-02T21:16:16.687Z"},
to this:
{"sequentialNumber": 1,"id": 61301,"name": "molestiae", "preparedAt": "2020-04-01"}
I have a search action in my code too, but it's working well.
Here are my JSON file and code:
jsonFile.json
"preppizzas": [
{
"sequentialNumber": 1,
"id": 61301,
"name": "molestiae",
"preparedAt": "2020-05-02T21:16:16.687Z"
},
{
"sequentialNumber": 2,
"id": 21349,
"name": "impedit",
"preparedAt": "2020-05-02T23:25:48.105Z"
},
{
"sequentialNumber": 3,
"id": 10235,
"name": "beatae",
"preparedAt": "2020-05-02T21:33:28.759Z"
},
{
"sequentialNumber": 4,
"id": 99688,
"name": "dicta",
"preparedAt": "2020-05-02T19:24:48.462Z"
}
]
PizzaDispatcher.js
import {Dispatcher} from 'flux';
import axios from 'axios';
import Preparationstore from "./stores/PreparationStore";
class PizzaDispatcher extends Dispatcher{
handleViewAction(action){
this.dispatch({
action : action
});
}
}
const dispatcher = new PizzaDispatcher();
dispatcher.register((payload)=>{
if(payload.action.actionType === 'PREPARATION_SEARCH') {
if (payload.action.payload.id === "") {
axios.get('/preppizzas').then((resp)=>{
Preparationstore._preparations = resp.data.filter((preparation)=>{
return preparation.preparedAt.includes(payload.action.payload.preparedAt)
});
Preparationstore.emitChange();
})
}
else {
axios.get('/preppizzas').then((resp) => {
Preparationstore._preparations = resp.data.filter((preparation) => {
return preparation.id == payload.action.payload.id;
});
Preparationstore.emitChange();
})
}
}
if(payload.action.actionType === 'PREPARATION_UPDATE'){
if(payload.action.payload.id !==''){
axios.put('/preppizzas/' + payload.action.payload.id,{
preparedAt : payload.action.payload.preparedAt,
}).then(resp=>{console.log(resp.data)}).catch(err => {console.log(err)
});
}
}
});
export default dispatcher;
PreparationActions.js
import dispatcher from '../PizzaDispatcher'
class PreparationActions{
search(id, preparedAt){
dispatcher.handleViewAction({
actionType : 'PREPARATION_SEARCH',
payload : {
id : id,
preparedAt : preparedAt
}
});
}
update(preparedAt){
dispatcher.handleViewAction({
actionType : 'PREPARATION_UPDATE',
payload : {
preparedAt : preparedAt
}
});
}
}
export default new PreparationActions;
PreparationStore.js
import EventEmitter from 'events'
class PreparationStore extends EventEmitter{
_preparations = [];
emitChange(){
this.emit('Change');
}
addChangeListener(callback){
this.on('Change', callback);
}
removeChangeListener(callback){
this.removeListener('Change', callback);
}
}
var Preparationstore = new PreparationStore();
export default Preparationstore;
import React from 'react';
import PreparationActions from "../../../../actions/PreparationActions";
PreparationsCRUD.js
class PreparationsCRUD extends React.Component{
constructor(){
super();
this.state={
id : "",
preparedAt : ""
}
}
render(){
return(
<div>
<table>
<tr>
<td><input
type={"number"} min="0" placeholder="ID"
value={this.state.id}
onChange={(e)=>{
let st = this.state;
st.id = e.target.value;
this.setState(st);}
}
onKeyDown={(e) => {
if (e.key === 'Enter') {
PreparationActions.search(this.state.id, this.state.preparedAt);
}
}}
/></td>
</tr>
<tr>
<td><input type={"text"} placeholder="Prepared at"
value={this.state.preparedAt}
onChange={(e)=>{
let st = this.state;
st.preparedAt = e.target.value;
this.setState(st);
}}
/></td>
</tr>
<tr>
<td
colSpan={2}>
<button
className="btn btn-info"
onClick={()=>{PreparationActions.search(this.state.id, this.state.preparedAt)
}}
>Search by ID
</button>
<button
className="btn btn-info"
onClick={()=>{
PreparationActions.update(
this.state.preparedAt,
);window.location.reload();}}
>Update
</button>
</td>
</tr>
</table>
</div>
);
}
}
export default PreparationsCRUD;
PreparationsResults.js
import React from 'react';
import Preparationstore from "../../../../stores/PreparationStore";
class PreparationsResult extends React.Component{
constructor(){
super();
this.state = {preparations : []};
this._onChange = this._onChange.bind(this);
}
_onChange(){
this.setState({preparations : Preparationstore._preparations})
}
componentDidMount(){
Preparationstore.addChangeListener(this._onChange)
}
componentWillUnmount(){
Preparationstore.removeChangeListener(this._onChange);
}
render(){
return(
<table className="table table-dark">
<thead>
<tr>
<td>Sequential number</td>
<td>Id</td>
<td>Name</td>
<td>Prepared at</td>
</tr>
</thead>
<tbody>
{
this.state.preparations.map((preparation)=>{
return(
<tr key={preparation.sequentialNumber}>
<td>{preparation.sequentialNumber}</td>
<td>{preparation.id}</td>
<td>{preparation.name}</td>
<td>{preparation.preparedAt}</td>
</tr>
);
})
}
</tbody>
</table>
);
}
}
export default PreparationsResult;
I literally just learned this earlier today!
You can use the spread operator in a reducer to keep all of the object the same and modify only one parameter. Hope this helps.
const personReducer = (person, action) => {
switch (action.type) {
case 'INCREASE_AGE':
return { ...person, age: person.age + 1 };
case 'CHANGE_LASTNAME':
return { ...person, lastname: action.payload.lastname };
default:
return person;
}
};
I have this module:
import React, { Component } from 'react'
import EmailListItem from './EmailListItem'
import { createContainer } from 'meteor/react-meteor-data'
import { Emails } from '../../../../../imports/collections/emails/Emails'
class EmailList extends Component {
constructor (props) {
super(props)
this.state = {
selectedEmails: new Set(),
checked: false
}
}
handleSelectedEmails (selectedEmail, checked) {
let selectedEmails = this.state.selectedEmails
if (checked) {
selectedEmails.add(selectedEmail)
} else {
selectedEmails.delete(selectedEmail)
}
this.setState({selectedEmails})
console.log('selectedEmails', this.state.selectedEmails)
}
removeSelected () {
const selectedEmails = Array.from(this.state.selectedEmails)
Meteor.call('emails.remove', selectedEmails, (err, result) => {
if (err) console.log(err)
if (result) console.log(result)
})
}
checkedClick () {
this.setState({checked: !this.state.checked})
console.log('chcekedClick')
}
renderList () {
console.log(this.props)
return this.props.emails.map(email => {
console.log(email)
const { name, opr, ctr, _id } = email
const createdAt = email.createdAt.toDateString()
const link = `/dashboard/emailpreview/${_id}`
return (
<EmailListItem
selecetedAllEmails={this.state.checked}
handleSelectedEmails={this.handleSelectedEmails.bind(this)}
name={name}
createdAt={createdAt}
opr={opr}
ctr={ctr}
link={link}
key={email._id}
id={email._id} />
)
})
}
render () {
// TODO: make checks with state
return (
<div className="email_list">
<table>
<thead>
<tr>
<td><input onChange={this.checkedClick.bind(this)} type="checkbox" checked={this.state.checked} /></td>
<td>Title<button onClick={this.removeSelected.bind(this)} className="btn btn-danger">Remove</button></td>
<td>Dates</td>
<td>Open Rates</td>
<td>CTA</td>
</tr>
</thead>
<tbody>
{this.renderList()}
</tbody>
</table>
</div>
)
}
}
export default createContainer(() => {
Meteor.subscribe('emails')
return { emails: Emails.find({}).fetch() }
}, EmailList)
And it renders this module
import React, { Component } from 'react'
import { Link } from 'react-router'
class EmailListItem extends Component {
constructor (props) {
super(props)
this.state = {
checked: false
}
}
checkedClick () {
this.setState({checked: !this.state.checked})
console.log('chcekedClick')
}
componentDidUpdate () {
console.log('componentDidUpdate')
const { myCheckbox } = this.refs
console.log('myCheckbox', myCheckbox)
console.log('myCheckbox.name', myCheckbox.name)
console.log('myCheckbox.checked', myCheckbox.checked)
if (this.props.selecetedAllEmails) {
console.log('componentDidUpdate IF')
this.checkedClick()
this.props.handleSelectedEmails(myCheckbox.name, myCheckbox.checked)
}
}
render () {
console.log('_id', this.props.id)
return (
<tr>
<td><input ref="myCheckbox"
onChange={(event) => {
this.checkedClick()
this.props.handleSelectedEmails(event.target.name, event.target.checked)
}}
checked={this.state.checked}
type="checkbox" name={this.props.id} /></td>
<td><Link to={this.props.link}>{this.props.name}</Link></td>
<td>{this.props.createdAt}</td>
<td>Open Rates</td>
<td>CTA</td>
</tr>
)
}
}
export default EmailListItem
As you can see, for each email item I have a checkbox. I can select a few checkboxes, and click that remove button which will call remove my selected items. Now in the top I have a checkbox which should select all the checkboxes. My solution to this was to store the global checkbox checked and pass it as a prop to all the items. Then in the items I perform a check on componentDidUpdate and if the global checkbox is selected then I check that item as well. But this results in an infinite loop. What would be the best solution here?
While some of the answers provided the specific functionality of selecting all the checkboxes, I needed also common functionalities like deselecting all, selecting all then deselecting some, when manually selecting all the select all box checks on as well etc... So I wrote all of that and post it as an answer here. Thanks to everyone who replied. This code is based on Mayank Shuklas answer. Note that it may not still be perfect as I didn't properly tested it yet and definitely it needs some refactoring.
import React, { Component } from 'react'
import EmailListItem from './EmailListItem'
import { createContainer } from 'meteor/react-meteor-data'
import { Emails } from '../../../../../imports/collections/emails/Emails'
class EmailList extends Component {
constructor (props) {
super(props)
this.state = {
selectedEmails: new Set(),
checked: false
}
}
handleSelectedEmails (allSelected, individualSelected, selectedEmail, checked) {
console.log('allSelected', allSelected)
console.log('individualSelected', individualSelected)
console.log('selectedEmail', selectedEmail)
console.log('checked', checked)
let selectedEmails = this.state.selectedEmails
if (allSelected && !individualSelected) {
this.props.emails.forEach((email) => {
selectedEmails.add(email._id)
})
} else if (!allSelected && !individualSelected) {
selectedEmails.clear()
} else if (individualSelected) {
if (checked) {
selectedEmails.add(selectedEmail)
if (selectedEmails.size === this.props.emails.length) {
this.checkAll()
}
} else {
selectedEmails.delete(selectedEmail)
this.setState({checked})
}
}
this.setState({selectedEmails})
console.log('selectedEmails', this.state.selectedEmails)
}
removeSelected () {
const selectedEmails = Array.from(this.state.selectedEmails)
Meteor.call('emails.remove', selectedEmails, (err, result) => {
if (err) console.log(err)
if (result) console.log(result)
})
}
checkAll () {
this.setState({checked: !this.state.checked})
console.log('chcekedClick', this.state.checked)
this.handleSelectedEmails(!this.state.checked, false)
}
renderList () {
console.log(this.props)
return this.props.emails.map(email => {
// console.log(email)
const { name, opr, ctr, _id } = email
const createdAt = email.createdAt.toDateString()
const link = `/dashboard/emailpreview/${_id}`
return (
<EmailListItem
handleSelectedEmails={this.handleSelectedEmails.bind(this)}
name={name}
createdAt={createdAt}
opr={opr}
ctr={ctr}
link={link}
key={email._id}
id={email._id}
value={this.state.checked || this.state.selectedEmails.has(email._id)} />
)
})
}
render () {
// TODO: make checks with state
return (
<div className="email_list">
<table>
<thead>
<tr>
<td><input onChange={this.checkAll.bind(this)} type="checkbox" checked={this.state.checked} /></td>
<td>Title<button onClick={this.removeSelected.bind(this)} className="btn btn-danger">Remove</button></td>
<td>Dates</td>
<td>Open Rates</td>
<td>CTA</td>
</tr>
</thead>
<tbody>
{this.renderList()}
</tbody>
</table>
</div>
)
}
}
export default createContainer(() => {
Meteor.subscribe('emails')
return { emails: Emails.find({}).fetch() }
}, EmailList)
And the EmailListItem
import React, { Component } from 'react'
import { Link } from 'react-router'
class EmailListItem extends Component {
render () {
console.log('_id', this.props.id)
return (
<tr>
<td><input ref="myCheckbox"
onChange={(event) => {
this.props.handleSelectedEmails(false, true, event.target.name, event.target.checked)
}}
checked={this.props.value}
type="checkbox" name={this.props.id} /></td>
<td><Link to={this.props.link}>{this.props.name}</Link></td>
<td>{this.props.createdAt}</td>
<td>Open Rates</td>
<td>CTA</td>
</tr>
)
}
}
export default EmailListItem
I think, maintaining individual states for each email id is not required, you are already storing the values in parent component, pass the value from parent in props, other thing is for selecting all email ids, you are maintaining a bool in parent, at the time of passing the value in props check that bool, if the bool is true then pass true otherwise check in the set and pass the result returned by set.
Check the working solution of jsfiddle: https://jsfiddle.net/h17mcjwa/
try this renderList method:
renderList () {
return this.props.emails.map(email => {
const { name, opr, ctr, _id } = email
const createdAt = email.createdAt.toDateString()
const link = `/dashboard/emailpreview/${_id}`
return (
<EmailListItem
handleSelectedEmails={this.handleSelectedEmails.bind(this)}
name={name}
createdAt={createdAt}
opr={opr}
ctr={ctr}
link={link}
key={email._id}
id={email._id}
value={this.state.checked || this.state.selectedEmails.has(email._id)}
/>
)
})
}
And use this component:
class EmailListItem extends Component {
constructor (props) {
super(props)
//this.state = {
// checked: false
//}
}
//checkedClick () {
// this.setState({checked: !this.state.checked})
// console.log('chcekedClick')
//}
//componentDidUpdate () {
// if (this.props.selecetedAllEmails) {
// this.checkedClick()
// this.props.handleSelectedEmails(myCheckbox.name, myCheckbox.checked)
// }
//}
render () {
return (
<tr>
<td><input ref="myCheckbox"
onChange={(event) => {
this.props.handleSelectedEmails(event.target.name, event.target.checked)
}}
checked={this.props.value}
type="checkbox" name={this.props.id} /></td>
<td><Link to={this.props.link}>{this.props.name}</Link></td>
<td>{this.props.createdAt}</td>
<td>Open Rates</td>
<td>CTA</td>
</tr>
)
}
}
Let me know if it doesn't work for u.
You could use componentWillReceiveProps instead of componentDidUpdate:
class EmailListsItem extends Component {
// ...
componentWillReceiveProps (nextProps) {
const { myCheckbox } = this.refs
// if selecetedAllEmails is updated from false to true
if (nextProps.selecetedAllEmails && !this.props.selecetedAllEmails) {
this.checkedClick()
this.props.handleSelectedEmails(myCheckbox.name, myCheckbox.checked)
}
}
// ...
}